Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion apps/swift-ios/Core/HTTP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,8 @@ public actor EnvironmentAPI {
)
}

/// `path` is percent-encoded: callers escape dynamic segments themselves so
/// an ID containing `%` or `/` stays one literal segment.
func authorized<Result: Decodable & Sendable>(
environment: Environment,
path: String,
Expand Down Expand Up @@ -504,7 +506,7 @@ public actor EnvironmentAPI {
headers: [String: String] = [:]
) -> URLRequest {
var request = URLRequest(
url: endpoint(environment.httpBaseURL, path: path, queryItems: queryItems)
url: endpoint(environment.httpBaseURL, percentEncodedPath: path, queryItems: queryItems)
)
request.httpMethod = method
request.httpBody = body
Expand Down Expand Up @@ -723,3 +725,11 @@ func endpoint(_ baseURL: URL, path: String, queryItems: [URLQueryItem] = []) ->
components.fragment = nil
return components.url!
}

func endpoint(_ baseURL: URL, percentEncodedPath: String, queryItems: [URLQueryItem] = []) -> URL {
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
components.percentEncodedPath = percentEncodedPath
components.queryItems = queryItems.isEmpty ? nil : queryItems
components.fragment = nil
return components.url!
}
12 changes: 12 additions & 0 deletions apps/swift-ios/Tests/CoreTests/DualOrchestrationClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ final class V2OrchestrationClientTests: XCTestCase {
XCTAssertEqual(history.value(forHTTPHeaderField: "x-t3-orchestration-protocol"), "2")
}

func testThreadReadEncodesLiteralPercentInThreadIDOnce() async throws {
let transport = try V2ClientHTTPTransport()
let client = makeClient(transport: transport)
_ = try? await client.threadSnapshot(id: "thread:delegated-task:command%3Amcp%3Aissue-17643")
let requests = await transport.requests
let read = try XCTUnwrap(requests.last { $0.url?.path.hasSuffix("/bounded") == true })
XCTAssertEqual(
URLComponents(url: try XCTUnwrap(read.url), resolvingAgainstBaseURL: false)?.percentEncodedPath,
"/api/orchestration/threads/thread:delegated-task:command%253Amcp%253Aissue-17643/bounded"
)
}

func testV2ConnectionSendsCanonicalLaunchCommand() async throws {
let transport = try V2ClientHTTPTransport()
let socket = V2ClientCommandSocket()
Expand Down
Loading