diff --git a/Sources/Vapor/HTTP/Headers/HTTPCookies.swift b/Sources/Vapor/HTTP/Headers/HTTPCookies.swift index 72389b49dd..9ad597f598 100644 --- a/Sources/Vapor/HTTP/Headers/HTTPCookies.swift +++ b/Sources/Vapor/HTTP/Headers/HTTPCookies.swift @@ -3,7 +3,7 @@ extension HTTPHeaders { /// This accesses the `"Cookie"` header. public var cookie: HTTPCookies? { get { - self.parseDirectives(name: .cookie).first.flatMap { + self.parseDirectives(name: .cookie).first.map { HTTPCookies(directives: $0) } } @@ -238,14 +238,12 @@ public struct HTTPCookies: ExpressibleByDictionaryLiteral { self.cookies = [:] } - init?(directives: [HTTPHeaders.Directive]) { - self.cookies = [:] - for directive in directives { - guard let value = directive.parameter else { - return nil + init(directives: [HTTPHeaders.Directive]) { + self.cookies = directives.reduce(into: [:], { (cookies, directive) in + if let value = directive.parameter { + cookies[.init(directive.value)] = .init(string: .init(value)) } - self.cookies[.init(directive.value)] = .init(string: .init(value)) - } + }) } /// See `ExpressibleByDictionaryLiteral`. diff --git a/Sources/Vapor/HTTP/Headers/HTTPHeaders+Directive.swift b/Sources/Vapor/HTTP/Headers/HTTPHeaders+Directive.swift index c7f0eb1c75..e4e9e94177 100644 --- a/Sources/Vapor/HTTP/Headers/HTTPHeaders+Directive.swift +++ b/Sources/Vapor/HTTP/Headers/HTTPHeaders+Directive.swift @@ -245,9 +245,12 @@ private extension Character { static var space: Self { .init(" ") } + static var percent: Self { + .init("%") + } var isDirectiveKey: Bool { - self.isLetter || self.isNumber || self == .dash || self == .underscore || self == .period + self.isLetter || self.isNumber || self == .dash || self == .underscore || self == .period || self == .percent } } diff --git a/Tests/VaporTests/HTTPHeaderTests.swift b/Tests/VaporTests/HTTPHeaderTests.swift index b630c75c05..f3c33f40a7 100644 --- a/Tests/VaporTests/HTTPHeaderTests.swift +++ b/Tests/VaporTests/HTTPHeaderTests.swift @@ -185,6 +185,27 @@ final class HTTPHeaderTests: XCTestCase { XCTAssertEqual(headers.cookie?["cookie_one"]?.string, "1") XCTAssertEqual(headers.cookie?["cookie.two"]?.string, "2") } + + func testCookie_percentParsing() throws { + let headers = HTTPHeaders([ + ("cookie", "cookie_one=1;cookie%40cookieCom=2;cookie_3=three") + ]) + + XCTAssertEqual(headers.cookie?["cookie_one"]?.string, "1") + XCTAssertEqual(headers.cookie?["cookie%40cookieCom"]?.string, "2") + XCTAssertEqual(headers.cookie?["cookie_3"]?.string, "three") + } + + func testCookie_invalidCookie() throws { + let headers = HTTPHeaders([ + ("cookie", "cookie_one=1;cookie\ntwo=2;cookie_three=3") + ]) + + XCTAssertEqual(headers.cookie?.all.count, 2) + XCTAssertEqual(headers.cookie?["cookie_one"]?.string, "1") + XCTAssertNil(headers.cookie?["cookie\ntwo"]) + XCTAssertEqual(headers.cookie?["cookie_three"]?.string, "3") + } func testMediaTypeMainTypeCaseInsensitive() throws { let lower = HTTPMediaType(type: "foo", subType: "")