Skip to content

Commit

Permalink
Support Percent Encoded Paths
Browse files Browse the repository at this point in the history
  • Loading branch information
swhitty committed Nov 23, 2023
1 parent 148e228 commit d8fac8f
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 4 deletions.
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

0 comments on commit d8fac8f

Please sign in to comment.