Skip to content

Commit 9870d94

Browse files
Ensure HTTPCookie domain matching conforms to RFC6265
Fix an issue when HTTPCookie domain starts with ".", cookies are not being sent with HTTP requests. Also, force domain matching to be in all lower case.
1 parent 91e5c25 commit 9870d94

File tree

3 files changed

+77
-6
lines changed

3 files changed

+77
-6
lines changed

Foundation/HTTPCookie.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ open class HTTPCookie : NSObject {
246246
_path = path
247247
_name = name
248248
_value = value
249-
_domain = canonicalDomain
249+
_domain = canonicalDomain.lowercased()
250250

251251
if let
252252
secureString = properties[.secure] as? String, !secureString.isEmpty

Foundation/HTTPCookieStorage.swift

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,8 @@ open class HTTPCookieStorage: NSObject {
267267
into a set of header fields to add to a request.
268268
*/
269269
open func cookies(for url: URL) -> [HTTPCookie]? {
270-
guard let host = url.host else { return nil }
271-
return Array(self.syncQ.sync(execute: {allCookies}).values.filter{ $0.domain == host })
270+
guard let host = url.host?.lowercased() else { return nil }
271+
return Array(self.syncQ.sync(execute: {allCookies}).values.filter{ $0.validFor(host: host) })
272272
}
273273

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

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

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

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

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

336336
extension HTTPCookie {
337+
internal func validFor(host: String) -> Bool {
338+
// RFC6265 - HTTP State Management Mechanism
339+
// https://tools.ietf.org/html/rfc6265#section-5.1.3
340+
//
341+
// 5.1.3. Domain Matching
342+
// A string domain-matches a given domain string if at least one of the
343+
// following conditions hold:
344+
//
345+
// 1) The domain string and the string are identical. (Note that both
346+
// the domain string and the string will have been canonicalized to
347+
// lower case at this point.)
348+
//
349+
// 2) All of the following conditions hold:
350+
// * The domain string is a suffix of the string.
351+
// * The last character of the string that is not included in the
352+
// domain string is a %x2E (".") character.
353+
// * The string is a host name (i.e., not an IP address).
354+
355+
let dotlessDomain = domain.first == "." ? String(domain.suffix(domain.count - 1)) : domain
356+
return dotlessDomain == host || (domain.first == "." && host.hasSuffix(domain))
357+
}
358+
337359
internal func persistableDictionary() -> [String: Any] {
338360
var properties: [String: Any] = [:]
339361
properties[HTTPCookiePropertyKey.name.rawValue] = name

TestFoundation/TestHTTPCookieStorage.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class TestHTTPCookieStorage: XCTestCase {
2626
("test_cookiesForURLWithMainDocumentURL", test_cookiesForURLWithMainDocumentURL),
2727
("test_cookieInXDGSpecPath", test_cookieInXDGSpecPath),
2828
("test_descriptionCookie", test_descriptionCookie),
29+
("test_cookieDomainMatching", test_cookieDomainMatching),
2930
]
3031
}
3132

@@ -89,6 +90,11 @@ class TestHTTPCookieStorage: XCTestCase {
8990
descriptionCookie(with: .groupContainer("test"))
9091
}
9192

93+
func test_cookieDomainMatching() {
94+
cookieDomainMatching(with: .shared)
95+
cookieDomainMatching(with: .groupContainer("test"))
96+
}
97+
9298
func getStorage(for type: _StorageType) -> HTTPCookieStorage {
9399
switch type {
94100
case .shared:
@@ -272,6 +278,49 @@ class TestHTTPCookieStorage: XCTestCase {
272278
XCTAssertEqual(storage.description, "<NSHTTPCookieStorage cookies count:\(cookies1.count)>")
273279
}
274280

281+
func cookieDomainMatching(with storageType: _StorageType) {
282+
let storage = getStorage(for: storageType)
283+
284+
let simpleCookie1 = HTTPCookie(properties: [ // swift.org domain only
285+
.name: "TestCookie1",
286+
.value: "TestValue1",
287+
.path: "/",
288+
.domain: "swift.org",
289+
])!
290+
291+
storage.setCookie(simpleCookie1)
292+
293+
let simpleCookie2 = HTTPCookie(properties: [ // *.swift.org
294+
.name: "TestCookie2",
295+
.value: "TestValue2",
296+
.path: "/",
297+
.domain: ".SWIFT.org",
298+
])!
299+
300+
storage.setCookie(simpleCookie2)
301+
302+
let simpleCookie3 = HTTPCookie(properties: [ // bugs.swift.org
303+
.name: "TestCookie3",
304+
.value: "TestValue3",
305+
.path: "/",
306+
.domain: "bugs.swift.org",
307+
])!
308+
309+
storage.setCookie(simpleCookie3)
310+
XCTAssertEqual(storage.cookies!.count, 3)
311+
312+
let swiftOrgUrl = URL(string: "https://swift.ORG")!
313+
let ciSwiftOrgUrl = URL(string: "https://CI.swift.ORG")!
314+
let bugsSwiftOrgUrl = URL(string: "https://BUGS.swift.org")!
315+
let exampleComUrl = URL(string: "http://www.example.com")!
316+
let superSwiftOrgUrl = URL(string: "https://superswift.org")!
317+
XCTAssertEqual(storage.cookies(for: swiftOrgUrl)!.count, 2)
318+
XCTAssertEqual(storage.cookies(for: ciSwiftOrgUrl)!.count, 1)
319+
XCTAssertEqual(storage.cookies(for: bugsSwiftOrgUrl)!.count, 2)
320+
XCTAssertEqual(storage.cookies(for: exampleComUrl)!.count, 0)
321+
XCTAssertEqual(storage.cookies(for: superSwiftOrgUrl)!.count, 0)
322+
}
323+
275324
func test_cookieInXDGSpecPath() {
276325
#if !os(Android) && !DARWIN_COMPATIBILITY_TESTS // No XDG on native Foundation
277326
//Test without setting the environment variable

0 commit comments

Comments
 (0)