Skip to content

Commit 688c690

Browse files
authored
Merge pull request #75 from swhitty/percent-encoded-path
Support Percent Encoded Paths
2 parents 148e228 + d8fac8f commit 688c690

File tree

4 files changed

+72
-4
lines changed

4 files changed

+72
-4
lines changed

FlyingFox/Sources/HTTPDecoder.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ struct HTTPDecoder {
8888
}
8989

9090
static func makeComponents(from comps: URLComponents?) -> (path: String, query: [HTTPRequest.QueryItem]) {
91-
let path = (comps?.path).flatMap { URL(string: $0)?.standardized.path } ?? ""
91+
let path = (comps?.percentEncodedPath).flatMap { URL(string: $0)?.standardized.path } ?? ""
9292
let query = comps?.queryItems?.map {
9393
HTTPRequest.QueryItem(name: $0.name, value: $0.value ?? "")
9494
}

FlyingFox/Sources/HTTPRoute.swift

+16-3
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public struct HTTPRoute: Sendable {
4949

5050
init(method: String, path: String, headers: [HTTPHeader: String], body: HTTPBodyPattern?) {
5151
self.method = Component(method)
52-
let comps = HTTPDecoder.readComponents(from: path)
52+
53+
let comps = HTTPRoute.readComponents(from: path)
5354
self.path = comps.path
5455
.split(separator: "/", omittingEmptySubsequences: true)
5556
.map { Component(String($0)) }
@@ -167,8 +168,8 @@ public extension HTTPRoute {
167168
}
168169

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

188+
private extension HTTPRoute {
189+
190+
static func readComponents(from path: String) -> (path: String, query: [HTTPRequest.QueryItem]) {
191+
guard path.removingPercentEncoding == path else {
192+
return HTTPDecoder.readComponents(from: path)
193+
}
194+
195+
let escaped = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
196+
return HTTPDecoder.readComponents(from: escaped ?? path)
197+
}
198+
}
199+
187200
extension HTTPRoute: ExpressibleByStringLiteral {
188201

189202
public init(stringLiteral value: String) {

FlyingFox/Tests/HTTPDecoderTests.swift

+22
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,28 @@ final class HTTPDecoderTests: XCTestCase {
218218
)
219219
}
220220

221+
func testPercentEncodedPathDecodes() {
222+
XCTAssertEqual(
223+
HTTPDecoder.readComponents(from: "/fish%20chips").path,
224+
"/fish chips"
225+
)
226+
XCTAssertEqual(
227+
HTTPDecoder.readComponents(from: "/ocean/fish%20and%20chips").path,
228+
"/ocean/fish and chips"
229+
)
230+
}
231+
232+
func testPercentQueryStringDecodes() {
233+
XCTAssertEqual(
234+
HTTPDecoder.readComponents(from: "/?fish=%F0%9F%90%9F").query,
235+
[.init(name: "fish", value: "🐟")]
236+
)
237+
XCTAssertEqual(
238+
HTTPDecoder.readComponents(from: "?%F0%9F%90%A1=chips").query,
239+
[.init(name: "🐡", value: "chips")]
240+
)
241+
}
242+
221243
func testEmptyQueryItem_Decodes() {
222244
var urlComps = URLComponents()
223245
urlComps.queryItems = [.init(name: "name", value: nil)]

FlyingFox/Tests/HTTPRouteTests.swift

+33
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,39 @@ final class HTTPRouteTests: XCTestCase {
6262
)
6363
}
6464

65+
func testPercentEncodedPathComponents() {
66+
XCTAssertEqual(
67+
HTTPRoute("GET /hello world").path,
68+
[.caseInsensitive("hello world")]
69+
)
70+
71+
XCTAssertEqual(
72+
HTTPRoute("/hello%20world").path,
73+
[.caseInsensitive("hello world")]
74+
)
75+
76+
XCTAssertEqual(
77+
HTTPRoute("🐡/*").path,
78+
[.caseInsensitive("🐡"), .wildcard]
79+
)
80+
81+
XCTAssertEqual(
82+
HTTPRoute("%F0%9F%90%A1/*").path,
83+
[.caseInsensitive("🐡"), .wildcard]
84+
)
85+
}
86+
87+
func testPercentEncodedQueryItems() {
88+
XCTAssertEqual(
89+
HTTPRoute("/?fish=%F0%9F%90%9F").query,
90+
[.init(name: "fish", value: .caseInsensitive("🐟"))]
91+
)
92+
XCTAssertEqual(
93+
HTTPRoute("/?%F0%9F%90%A1=chips").query,
94+
[.init(name: "🐡", value: .caseInsensitive("chips"))]
95+
)
96+
}
97+
6598
func testMethod() {
6699
XCTAssertEqual(
67100
HTTPRoute("hello/world").method,

0 commit comments

Comments
 (0)