Skip to content
Merged
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
2 changes: 1 addition & 1 deletion FlyingFox/Sources/HTTPDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ struct HTTPDecoder {
}

static func makeComponents(from comps: URLComponents?) -> (path: String, query: [HTTPRequest.QueryItem]) {
let path = (comps?.path).flatMap { URL(string: $0)?.standardized.path } ?? ""
let path = (comps?.percentEncodedPath).flatMap { URL(string: $0)?.standardized.path } ?? ""
let query = comps?.queryItems?.map {
HTTPRequest.QueryItem(name: $0.name, value: $0.value ?? "")
}
Expand Down
19 changes: 16 additions & 3 deletions FlyingFox/Sources/HTTPRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public struct HTTPRoute: Sendable {

init(method: String, path: String, headers: [HTTPHeader: String], body: HTTPBodyPattern?) {
self.method = Component(method)
let comps = HTTPDecoder.readComponents(from: path)

let comps = HTTPRoute.readComponents(from: path)
self.path = comps.path
.split(separator: "/", omittingEmptySubsequences: true)
.map { Component(String($0)) }
Expand Down Expand Up @@ -167,8 +168,8 @@ public extension HTTPRoute {
}

private static func components(for target: String) -> (method: String, path: String) {
let comps = target.split(separator: " ", maxSplits: 2, omittingEmptySubsequences: true)
guard comps.count > 1 else {
let comps = target.split(separator: " ", maxSplits: 1, omittingEmptySubsequences: true)
guard comps.count > 1 && !comps[0].hasPrefix("/") else {
return (method: "*", path: target)
}
return (method: String(comps[0]), path: String(comps[1]))
Expand All @@ -184,6 +185,18 @@ public extension HTTPRoute {
}
}

private extension HTTPRoute {

static func readComponents(from path: String) -> (path: String, query: [HTTPRequest.QueryItem]) {
guard path.removingPercentEncoding == path else {
return HTTPDecoder.readComponents(from: path)
}

let escaped = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
return HTTPDecoder.readComponents(from: escaped ?? path)
}
}

extension HTTPRoute: ExpressibleByStringLiteral {

public init(stringLiteral value: String) {
Expand Down
22 changes: 22 additions & 0 deletions FlyingFox/Tests/HTTPDecoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,28 @@ final class HTTPDecoderTests: XCTestCase {
)
}

func testPercentEncodedPathDecodes() {
XCTAssertEqual(
HTTPDecoder.readComponents(from: "/fish%20chips").path,
"/fish chips"
)
XCTAssertEqual(
HTTPDecoder.readComponents(from: "/ocean/fish%20and%20chips").path,
"/ocean/fish and chips"
)
}

func testPercentQueryStringDecodes() {
XCTAssertEqual(
HTTPDecoder.readComponents(from: "/?fish=%F0%9F%90%9F").query,
[.init(name: "fish", value: "🐟")]
)
XCTAssertEqual(
HTTPDecoder.readComponents(from: "?%F0%9F%90%A1=chips").query,
[.init(name: "🐡", value: "chips")]
)
}

func testEmptyQueryItem_Decodes() {
var urlComps = URLComponents()
urlComps.queryItems = [.init(name: "name", value: nil)]
Expand Down
33 changes: 33 additions & 0 deletions FlyingFox/Tests/HTTPRouteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,39 @@ final class HTTPRouteTests: XCTestCase {
)
}

func testPercentEncodedPathComponents() {
XCTAssertEqual(
HTTPRoute("GET /hello world").path,
[.caseInsensitive("hello world")]
)

XCTAssertEqual(
HTTPRoute("/hello%20world").path,
[.caseInsensitive("hello world")]
)

XCTAssertEqual(
HTTPRoute("🐡/*").path,
[.caseInsensitive("🐡"), .wildcard]
)

XCTAssertEqual(
HTTPRoute("%F0%9F%90%A1/*").path,
[.caseInsensitive("🐡"), .wildcard]
)
}

func testPercentEncodedQueryItems() {
XCTAssertEqual(
HTTPRoute("/?fish=%F0%9F%90%9F").query,
[.init(name: "fish", value: .caseInsensitive("🐟"))]
)
XCTAssertEqual(
HTTPRoute("/?%F0%9F%90%A1=chips").query,
[.init(name: "🐡", value: .caseInsensitive("chips"))]
)
}

func testMethod() {
XCTAssertEqual(
HTTPRoute("hello/world").method,
Expand Down