From 9bf8361e73399e7470e5c8380ef82bac29b1bfa0 Mon Sep 17 00:00:00 2001 From: swift-ci Date: Tue, 31 Jul 2018 02:26:39 -0700 Subject: [PATCH] Merge pull request #1630 from maksimorlovich/rfc6265-cookie-domain-matching --- Foundation/HTTPCookie.swift | 2 +- Foundation/HTTPCookieStorage.swift | 32 +++++++++++--- TestFoundation/TestHTTPCookieStorage.swift | 49 ++++++++++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/Foundation/HTTPCookie.swift b/Foundation/HTTPCookie.swift index 47a92d6e3c..902fd058da 100644 --- a/Foundation/HTTPCookie.swift +++ b/Foundation/HTTPCookie.swift @@ -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 diff --git a/Foundation/HTTPCookieStorage.swift b/Foundation/HTTPCookieStorage.swift index 2ef5d4703d..ced27f99d6 100644 --- a/Foundation/HTTPCookieStorage.swift +++ b/Foundation/HTTPCookieStorage.swift @@ -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) }) } /*! @@ -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) } @@ -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 diff --git a/TestFoundation/TestHTTPCookieStorage.swift b/TestFoundation/TestHTTPCookieStorage.swift index 0339f2cf4a..e6c4e1f913 100644 --- a/TestFoundation/TestHTTPCookieStorage.swift +++ b/TestFoundation/TestHTTPCookieStorage.swift @@ -26,6 +26,7 @@ class TestHTTPCookieStorage: XCTestCase { ("test_cookiesForURLWithMainDocumentURL", test_cookiesForURLWithMainDocumentURL), ("test_cookieInXDGSpecPath", test_cookieInXDGSpecPath), ("test_descriptionCookie", test_descriptionCookie), + ("test_cookieDomainMatching", test_cookieDomainMatching), ] } @@ -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: @@ -272,6 +278,49 @@ class TestHTTPCookieStorage: XCTestCase { XCTAssertEqual(storage.description, "") } + 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 //Test without setting the environment variable