diff --git a/Sources/HummingbirdCore/Utils/HBParser.swift b/Sources/HummingbirdCore/Utils/HBParser.swift index fdf6b67aa..b09342e6d 100644 --- a/Sources/HummingbirdCore/Utils/HBParser.swift +++ b/Sources/HummingbirdCore/Utils/HBParser.swift @@ -596,8 +596,7 @@ extension Parser { func _percentDecode(_ original: ArraySlice, _ bytes: UnsafeMutableBufferPointer) throws -> Int { var newIndex = 0 var index = original.startIndex - - while index < original.endIndex { + while index < (original.endIndex - 2) { // if we have found a percent sign if original[index] == 0x25 { let high = Self.asciiHexValues[Int(original[index + 1])] @@ -614,9 +613,13 @@ extension Parser { index += 1 } } + while index < original.endIndex { + bytes[newIndex] = original[index] + newIndex += 1 + index += 1 + } return newIndex } - guard self.index != self.range.endIndex else { return "" } do { if #available(macOS 11, macCatalyst 14.0, iOS 14.0, tvOS 14.0, *) { diff --git a/Tests/HummingbirdRouterTests/RouterTests.swift b/Tests/HummingbirdRouterTests/RouterTests.swift index 20859f766..70b1ffdfc 100644 --- a/Tests/HummingbirdRouterTests/RouterTests.swift +++ b/Tests/HummingbirdRouterTests/RouterTests.swift @@ -310,6 +310,28 @@ final class RouterTests: XCTestCase { } } + /// Test the hummingbird core parser against possible overflows of the percent encoder. this issue was introduced in pr #404 in the context of query parameters but I've thrown in some other random overflow scenarios in here too for good measure. if it doesn't crash, its a win. + func testQueryParameterOverflow() async throws { + let router = RouterBuilder(context: BasicRouterRequestContext.self) { + Get("overflow") { req, _ in + let currentQP = req.uri.queryParameters["query"] + return String("\(currentQP ?? "")") + } + } + let app = Application(router: router) + try await app.test(.router) { client in + try await client.execute(uri: "/overflow?query=value%", method: .get) { response in + XCTAssertEqual(String(buffer: response.body), "value%") + } + try await client.execute(uri: "/overflow?query%=value%", method: .get) { response in + XCTAssertEqual(String(buffer: response.body), "") + } + try await client.execute(uri: "/overflow?%&", method: .get) { response in + XCTAssertEqual(String(buffer: response.body), "") + } + } + } + func testParameters() async throws { let router = RouterBuilder(context: BasicRouterRequestContext.self) { Delete("/user/:id") { _, context -> String? in