Skip to content

Commit 0e7e566

Browse files
author
Pushkar Kulkarni
committed
Initial implementation of HTTPCookieStorage
1 parent e57d3c6 commit 0e7e566

File tree

3 files changed

+160
-6
lines changed

3 files changed

+160
-6
lines changed

Foundation/NSHTTPCookieStorage.swift

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// See http://swift.org/LICENSE.txt for license information
77
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
9-
9+
import Dispatch
1010

1111
/*!
1212
@enum NSHTTPCookieAcceptPolicy
@@ -35,11 +35,33 @@ extension HTTPCookie {
3535
generate cookie-related HTTP header fields.
3636
*/
3737
open class HTTPCookieStorage: NSObject {
38-
39-
public override init() { NSUnimplemented() }
38+
39+
private static var sharedStorage: HTTPCookieStorage?
40+
41+
private let cookieFilePath: String = NSHomeDirectory() + "/.cookies"
42+
private let workQueue: DispatchQueue = DispatchQueue(label: "HTTPCookieStorage.workqueue")
43+
var allCookies: [String: HTTPCookie]
44+
45+
public override init() {
46+
allCookies = [:]
47+
cookieAcceptPolicy = .always
48+
super.init()
49+
if let cookies = NSMutableDictionary(contentsOfFile: cookieFilePath) {
50+
var cookies0 = _SwiftValue.fetch(cookies) as? [String: [String: Any]] ?? [:]
51+
for key in cookies0.keys {
52+
if let cookie = createCookie(cookies0[key]!) {
53+
allCookies[key] = cookie
54+
}
55+
}
56+
}
57+
}
4058

4159
open var cookies: [HTTPCookie]? {
42-
NSUnimplemented()
60+
var theCookies: [HTTPCookie]?
61+
workQueue.sync {
62+
theCookies = Array(self.allCookies.values)
63+
}
64+
return theCookies
4365
}
4466

4567
/*!
@@ -49,7 +71,14 @@ open class HTTPCookieStorage: NSObject {
4971
@discussion Starting in OS X 10.11, each app has its own sharedHTTPCookieStorage singleton,
5072
which will not be shared with other applications.
5173
*/
52-
class var shared: HTTPCookieStorage { get { NSUnimplemented() } }
74+
open class var shared: HTTPCookieStorage {
75+
get {
76+
if sharedStorage == nil {
77+
sharedStorage = HTTPCookieStorage()
78+
}
79+
return sharedStorage!
80+
}
81+
}
5382

5483
/*!
5584
@method sharedCookieStorageForGroupContainerIdentifier:
@@ -70,8 +99,48 @@ open class HTTPCookieStorage: NSObject {
7099
@discussion The cookie will override an existing cookie with the
71100
same name, domain and path, if any.
72101
*/
73-
open func setCookie(_ cookie: HTTPCookie) { NSUnimplemented() }
102+
open func setCookie(_ cookie: HTTPCookie) {
103+
workQueue.sync {
104+
if cookieAcceptPolicy == .never { return }
105+
106+
//add or replace
107+
let key = cookie.domain + cookie.path + cookie.name
108+
if let _ = allCookies.index(forKey: key) {
109+
allCookies.updateValue(cookie, forKey: key)
110+
} else {
111+
allCookies[key] = cookie
112+
}
113+
114+
//remove stale cookies, these may include the one we just added
115+
let expired = allCookies.filter { (_, value) in value.expiresDate != nil && value.expiresDate!.timeIntervalSinceNow < 0}
116+
expired.forEach { (key, _) in self.allCookies.removeValue(forKey: key) }
117+
118+
persistCookies()
119+
}
120+
}
74121

122+
private func createCookie(_ properties: [String: Any]) -> HTTPCookie? {
123+
var cookieProperties: [HTTPCookiePropertyKey: Any] = [:]
124+
properties.keys.forEach {
125+
if $0 == "Expires" {
126+
let value = properties[$0] as! NSNumber
127+
cookieProperties[HTTPCookiePropertyKey(rawValue: $0)] = Date(timeIntervalSince1970: value.doubleValue)
128+
} else {
129+
cookieProperties[HTTPCookiePropertyKey(rawValue: $0)] = properties[$0]
130+
}
131+
}
132+
return HTTPCookie(properties: cookieProperties)
133+
}
134+
135+
private func persistCookies() {
136+
//persist cookies
137+
var persistDictionary: [String : [String : Any]] = [:]
138+
allCookies.filter { (_, value) in value.expiresDate != nil && value.isSessionOnly == false && value.expiresDate!.timeIntervalSinceNow > 0 }
139+
.forEach { persistDictionary[$0.0] = $0.1.simpleDictionary() }
140+
let nsdict = _SwiftValue.store(persistDictionary) as! NSDictionary
141+
_ = nsdict.write(toFile: cookieFilePath, atomically: true)
142+
}
143+
75144
/*!
76145
@method deleteCookie:
77146
@abstract Delete the specified cookie
@@ -129,6 +198,7 @@ open class HTTPCookieStorage: NSObject {
129198
@discussion proper sorting of cookies may require extensive string conversion, which can be avoided by allowing the system to perform the sorting. This API is to be preferred over the more generic -[NSHTTPCookieStorage cookies] API, if sorting is going to be performed.
130199
*/
131200
open func sortedCookies(using sortOrder: [SortDescriptor]) -> [HTTPCookie] { NSUnimplemented() }
201+
132202
}
133203

134204
/*!
@@ -137,3 +207,16 @@ open class HTTPCookieStorage: NSObject {
137207
*/
138208
public let NSHTTPCookieManagerCookiesChangedNotification: String = "" // NSUnimplemented
139209

210+
extension HTTPCookie {
211+
public func simpleDictionary() -> [String: Any] {
212+
var properties: [String: Any] = [:]
213+
properties["Name"] = _name
214+
properties["Path"] = _path
215+
properties["Value"] = _value
216+
properties["Secure"] = _secure
217+
properties["Version"] = _version
218+
properties["Expires"] = _expiresDate!.timeIntervalSince1970
219+
properties["Domain"] = _domain
220+
return properties
221+
}
222+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
4+
// Licensed under Apache License v2.0 with Runtime Library Exception
5+
//
6+
// See http://swift.org/LICENSE.txt for license information
7+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8+
//
9+
10+
#if DEPLOYMENT_RUNTIME_OBJC || os(Linux)
11+
import Foundation
12+
import XCTest
13+
#else
14+
import SwiftFoundation
15+
import SwiftXCTest
16+
#endif
17+
18+
class TestNSHTTPCookieStorage: XCTestCase {
19+
20+
static var allTests: [(String, (TestNSHTTPCookieStorage) -> () throws -> Void)] {
21+
return [
22+
("test_BasicStorageAndRetrieval", test_BasicStorageAndRetrieval),
23+
]
24+
}
25+
26+
func test_BasicStorageAndRetrieval() {
27+
let storage = HTTPCookieStorage.shared
28+
29+
let simpleCookie = HTTPCookie(properties: [
30+
.name: "TestCookie1",
31+
.value: "Test value @#$%^$&*99",
32+
.path: "/",
33+
.domain: "swift.org",
34+
.expires: Date(timeIntervalSince1970: 1475767775) //expired cookie
35+
])!
36+
37+
storage.setCookie(simpleCookie)
38+
XCTAssertEqual(storage.cookies!.count, 0)
39+
40+
let simpleCookie0 = HTTPCookie(properties: [ //no expiry date
41+
.name: "TestCookie1",
42+
.value: "Test @#$%^$&*99",
43+
.path: "/",
44+
.domain: "swift.org",
45+
])!
46+
47+
storage.setCookie(simpleCookie0)
48+
XCTAssertEqual(storage.cookies!.count, 1)
49+
50+
let simpleCookie1 = HTTPCookie(properties: [
51+
.name: "TestCookie1",
52+
.value: "Test @#$%^$&*99",
53+
.path: "/",
54+
.domain: "swift.org",
55+
])!
56+
57+
storage.setCookie(simpleCookie1)
58+
XCTAssertEqual(storage.cookies!.count, 1) //test for replacement
59+
60+
let simpleCookie2 = HTTPCookie(properties: [
61+
.name: "TestCookie1",
62+
.value: "Test @#$%^$&*99",
63+
.path: "/",
64+
.domain: "example.com",
65+
])!
66+
67+
storage.setCookie(simpleCookie2)
68+
XCTAssertEqual(storage.cookies!.count, 2)
69+
}
70+
}

TestFoundation/main.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ XCTMain([
3636
testCase(TestNSFileManager.allTests),
3737
testCase(TestNSGeometry.allTests),
3838
testCase(TestNSHTTPCookie.allTests),
39+
testCase(TestNSHTTPCookieStorage.allTests),
3940
testCase(TestNSIndexPath.allTests),
4041
testCase(TestNSIndexSet.allTests),
4142
testCase(TestNSJSONSerialization.allTests),

0 commit comments

Comments
 (0)