Skip to content

Ensure HTTPCookie domain matching conforms to RFC6265 #1630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 31, 2018
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 Foundation/HTTPCookie.swift
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ open class HTTPCookie : NSObject {
_path = path
_name = name
_value = value
_domain = canonicalDomain
_domain = canonicalDomain.lowercased()

if let
secureString = properties[.secure] as? String, !secureString.isEmpty
Expand Down
32 changes: 27 additions & 5 deletions Foundation/HTTPCookieStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ open class HTTPCookieStorage: NSObject {
into a set of header fields to add to a request.
*/
open func cookies(for url: URL) -> [HTTPCookie]? {
guard let host = url.host else { return nil }
return Array(self.syncQ.sync(execute: {allCookies}).values.filter{ $0.domain == host })
guard let host = url.host?.lowercased() else { return nil }
return Array(self.syncQ.sync(execute: {allCookies}).values.filter{ $0.validFor(host: host) })
}

/*!
Expand All @@ -293,17 +293,17 @@ open class HTTPCookieStorage: NSObject {
guard cookieAcceptPolicy != .never else { return }

//if the urls don't have a host, we cannot do anything
guard let urlHost = url?.host else { return }
guard let urlHost = url?.host?.lowercased() else { return }

if mainDocumentURL != nil && cookieAcceptPolicy == .onlyFromMainDocumentDomain {
guard let mainDocumentHost = mainDocumentURL?.host else { return }
guard let mainDocumentHost = mainDocumentURL?.host?.lowercased() else { return }

//the url.host must be a suffix of manDocumentURL.host, this is based on Darwin's behaviour
guard mainDocumentHost.hasSuffix(urlHost) else { return }
}

//save only those cookies whose domain matches with the url.host
let validCookies = cookies.filter { urlHost == $0.domain }
let validCookies = cookies.filter { $0.validFor(host: urlHost) }
for cookie in validCookies {
setCookie(cookie)
}
Expand Down Expand Up @@ -334,6 +334,28 @@ public extension Notification.Name {
}

extension HTTPCookie {
internal func validFor(host: String) -> Bool {
// RFC6265 - HTTP State Management Mechanism
// https://tools.ietf.org/html/rfc6265#section-5.1.3
//
// 5.1.3. Domain Matching
// A string domain-matches a given domain string if at least one of the
// following conditions hold:
//
// 1) The domain string and the string are identical. (Note that both
// the domain string and the string will have been canonicalized to
// lower case at this point.)
//
// 2) All of the following conditions hold:
// * The domain string is a suffix of the string.
// * The last character of the string that is not included in the
// domain string is a %x2E (".") character.
// * The string is a host name (i.e., not an IP address).

guard domain.hasPrefix(".") else { return host == domain }
return host == domain.dropFirst() || host.hasSuffix(domain)
}

internal func persistableDictionary() -> [String: Any] {
var properties: [String: Any] = [:]
properties[HTTPCookiePropertyKey.name.rawValue] = name
Expand Down
49 changes: 49 additions & 0 deletions TestFoundation/TestHTTPCookieStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class TestHTTPCookieStorage: XCTestCase {
("test_cookiesForURLWithMainDocumentURL", test_cookiesForURLWithMainDocumentURL),
("test_cookieInXDGSpecPath", test_cookieInXDGSpecPath),
("test_descriptionCookie", test_descriptionCookie),
("test_cookieDomainMatching", test_cookieDomainMatching),
]
}

Expand Down Expand Up @@ -89,6 +90,11 @@ class TestHTTPCookieStorage: XCTestCase {
descriptionCookie(with: .groupContainer("test"))
}

func test_cookieDomainMatching() {
cookieDomainMatching(with: .shared)
cookieDomainMatching(with: .groupContainer("test"))
}

func getStorage(for type: _StorageType) -> HTTPCookieStorage {
switch type {
case .shared:
Expand Down Expand Up @@ -272,6 +278,49 @@ class TestHTTPCookieStorage: XCTestCase {
XCTAssertEqual(storage.description, "<NSHTTPCookieStorage cookies count:\(cookies1.count)>")
}

func cookieDomainMatching(with storageType: _StorageType) {
let storage = getStorage(for: storageType)

let simpleCookie1 = HTTPCookie(properties: [ // swift.org domain only
.name: "TestCookie1",
.value: "TestValue1",
.path: "/",
.domain: "swift.org",
])!

storage.setCookie(simpleCookie1)

let simpleCookie2 = HTTPCookie(properties: [ // *.swift.org
.name: "TestCookie2",
.value: "TestValue2",
.path: "/",
.domain: ".SWIFT.org",
])!

storage.setCookie(simpleCookie2)

let simpleCookie3 = HTTPCookie(properties: [ // bugs.swift.org
.name: "TestCookie3",
.value: "TestValue3",
.path: "/",
.domain: "bugs.swift.org",
])!

storage.setCookie(simpleCookie3)
XCTAssertEqual(storage.cookies!.count, 3)

let swiftOrgUrl = URL(string: "https://swift.ORG")!
let ciSwiftOrgUrl = URL(string: "https://CI.swift.ORG")!
let bugsSwiftOrgUrl = URL(string: "https://BUGS.swift.org")!
let exampleComUrl = URL(string: "http://www.example.com")!
let superSwiftOrgUrl = URL(string: "https://superswift.org")!
XCTAssertEqual(Set(storage.cookies(for: swiftOrgUrl)!), Set([simpleCookie1, simpleCookie2]))
XCTAssertEqual(storage.cookies(for: ciSwiftOrgUrl)!, [simpleCookie2])
XCTAssertEqual(Set(storage.cookies(for: bugsSwiftOrgUrl)!), Set([simpleCookie2, simpleCookie3]))
XCTAssertEqual(storage.cookies(for: exampleComUrl)!, [])
XCTAssertEqual(storage.cookies(for: superSwiftOrgUrl)!, [])
}

func test_cookieInXDGSpecPath() {
#if !os(Android) && !DARWIN_COMPATIBILITY_TESTS // No XDG on native Foundation
//Test without setting the environment variable
Expand Down