Skip to content

Commit 8c9b7a7

Browse files
author
Pushkar Kulkarni
committed
Initial implementation of HTTPCookieStorage
1 parent 41542b2 commit 8c9b7a7

File tree

4 files changed

+306
-13
lines changed

4 files changed

+306
-13
lines changed

Foundation.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/* Begin PBXBuildFile section */
1010
0383A1751D2E558A0052E5D1 /* TestNSStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0383A1741D2E558A0052E5D1 /* TestNSStream.swift */; };
1111
1520469B1D8AEABE00D02E36 /* HTTPServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1520469A1D8AEABE00D02E36 /* HTTPServer.swift */; };
12+
159884921DCC877700E3314C /* TestNSHTTPCookieStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159884911DCC877700E3314C /* TestNSHTTPCookieStorage.swift */; };
1213
231503DB1D8AEE5D0061694D /* TestNSDecimal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231503DA1D8AEE5D0061694D /* TestNSDecimal.swift */; };
1314
294E3C1D1CC5E19300E4F44C /* TestNSAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */; };
1415
2EBE67A51C77BF0E006583D5 /* TestNSDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBE67A31C77BF05006583D5 /* TestNSDateFormatter.swift */; };
@@ -466,6 +467,7 @@
466467
/* Begin PBXFileReference section */
467468
0383A1741D2E558A0052E5D1 /* TestNSStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSStream.swift; sourceTree = "<group>"; };
468469
1520469A1D8AEABE00D02E36 /* HTTPServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPServer.swift; sourceTree = "<group>"; };
470+
159884911DCC877700E3314C /* TestNSHTTPCookieStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestNSHTTPCookieStorage.swift; sourceTree = "<group>"; };
469471
22B9C1E01C165D7A00DECFF9 /* TestNSDate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSDate.swift; sourceTree = "<group>"; };
470472
231503DA1D8AEE5D0061694D /* TestNSDecimal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSDecimal.swift; sourceTree = "<group>"; };
471473
294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSAttributedString.swift; sourceTree = "<group>"; };
@@ -1338,6 +1340,7 @@
13381340
EA66F65A1BF1976100136161 /* Tests */ = {
13391341
isa = PBXGroup;
13401342
children = (
1343+
159884911DCC877700E3314C /* TestNSHTTPCookieStorage.swift */,
13411344
D4FE895A1D703D1100DA7986 /* TestURLRequest.swift */,
13421345
C93559281C12C49F009FD6A9 /* TestNSAffineTransform.swift */,
13431346
EA66F63C1BF1619600136161 /* TestNSArray.swift */,
@@ -2235,6 +2238,7 @@
22352238
isa = PBXSourcesBuildPhase;
22362239
buildActionMask = 2147483647;
22372240
files = (
2241+
159884921DCC877700E3314C /* TestNSHTTPCookieStorage.swift in Sources */,
22382242
5FE52C951D147D1C00F7D270 /* TestNSTextCheckingResult.swift in Sources */,
22392243
5B13B3451C582D4C00651CE2 /* TestNSString.swift in Sources */,
22402244
1520469B1D8AEABE00D02E36 /* HTTPServer.swift in Sources */,

Foundation/NSHTTPCookieStorage.swift

Lines changed: 154 additions & 13 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
@@ -18,7 +18,6 @@
1818
*/
1919
extension HTTPCookie {
2020
public enum AcceptPolicy : UInt {
21-
2221
case always
2322
case never
2423
case onlyFromMainDocumentDomain
@@ -35,11 +34,36 @@ extension HTTPCookie {
3534
generate cookie-related HTTP header fields.
3635
*/
3736
open class HTTPCookieStorage: NSObject {
38-
39-
public override init() { NSUnimplemented() }
40-
37+
38+
private static var sharedStorage: HTTPCookieStorage?
39+
40+
private let cookieFilePath: String = NSHomeDirectory() + "/.cookies"
41+
private let workQueue: DispatchQueue = DispatchQueue(label: "HTTPCookieStorage.workqueue")
42+
var allCookies: [String: HTTPCookie]
43+
44+
public override init() {
45+
allCookies = [:]
46+
cookieAcceptPolicy = .always
47+
super.init()
48+
loadPersistedCookies()
49+
}
50+
51+
private func loadPersistedCookies() {
52+
guard let cookies = NSMutableDictionary(contentsOfFile: cookieFilePath) else { return }
53+
var cookies0 = _SwiftValue.fetch(cookies) as? [String: [String: Any]] ?? [:]
54+
for key in cookies0.keys {
55+
if let cookie = createCookie(cookies0[key]!) {
56+
allCookies[key] = cookie
57+
}
58+
}
59+
}
60+
4161
open var cookies: [HTTPCookie]? {
42-
NSUnimplemented()
62+
var theCookies: [HTTPCookie]?
63+
workQueue.sync {
64+
theCookies = Array(self.allCookies.values)
65+
}
66+
return theCookies
4367
}
4468

4569
/*!
@@ -49,7 +73,14 @@ open class HTTPCookieStorage: NSObject {
4973
@discussion Starting in OS X 10.11, each app has its own sharedHTTPCookieStorage singleton,
5074
which will not be shared with other applications.
5175
*/
52-
class var shared: HTTPCookieStorage { get { NSUnimplemented() } }
76+
open class var shared: HTTPCookieStorage {
77+
get {
78+
if sharedStorage == nil {
79+
sharedStorage = HTTPCookieStorage()
80+
}
81+
return sharedStorage!
82+
}
83+
}
5384

5485
/*!
5586
@method sharedCookieStorageForGroupContainerIdentifier:
@@ -70,20 +101,82 @@ open class HTTPCookieStorage: NSObject {
70101
@discussion The cookie will override an existing cookie with the
71102
same name, domain and path, if any.
72103
*/
73-
open func setCookie(_ cookie: HTTPCookie) { NSUnimplemented() }
104+
open func setCookie(_ cookie: HTTPCookie) {
105+
workQueue.sync {
106+
if cookieAcceptPolicy == .never { return }
107+
108+
//add or replace
109+
let key = cookie.domain + cookie.path + cookie.name
110+
if let _ = allCookies.index(forKey: key) {
111+
allCookies.updateValue(cookie, forKey: key)
112+
} else {
113+
allCookies[key] = cookie
114+
}
115+
116+
//remove stale cookies, these may include the one we just added
117+
let expired = allCookies.filter { (_, value) in value.expiresDate != nil && value.expiresDate!.timeIntervalSinceNow < 0}
118+
for (key,_) in expired {
119+
self.allCookies.removeValue(forKey: key)
120+
}
121+
122+
updatePersistentStore()
123+
}
124+
}
74125

126+
private func createCookie(_ properties: [String: Any]) -> HTTPCookie? {
127+
var cookieProperties: [HTTPCookiePropertyKey: Any] = [:]
128+
for key in properties.keys {
129+
if key == "Expires" {
130+
let value = properties[key] as! NSNumber
131+
cookieProperties[HTTPCookiePropertyKey(rawValue: key)] = Date(timeIntervalSince1970: value.doubleValue)
132+
} else {
133+
cookieProperties[HTTPCookiePropertyKey(rawValue: key)] = properties[key]
134+
}
135+
}
136+
return HTTPCookie(properties: cookieProperties)
137+
}
138+
139+
private func updatePersistentStore() {
140+
//persist cookies
141+
var persistDictionary: [String : [String : Any]] = [:]
142+
let persistable = allCookies.filter { (_, value) in value.expiresDate != nil &&
143+
value.isSessionOnly == false &&
144+
value.expiresDate!.timeIntervalSinceNow > 0 }
145+
146+
for (key,cookie) in persistable {
147+
persistDictionary[key] = cookie.dictionary()
148+
}
149+
150+
let nsdict = _SwiftValue.store(persistDictionary) as! NSDictionary
151+
_ = nsdict.write(toFile: cookieFilePath, atomically: true)
152+
}
153+
75154
/*!
76155
@method deleteCookie:
77156
@abstract Delete the specified cookie
78157
*/
79-
open func deleteCookie(_ cookie: HTTPCookie) { NSUnimplemented() }
158+
open func deleteCookie(_ cookie: HTTPCookie) {
159+
workQueue.sync {
160+
let key = cookie.domain + cookie.path + cookie.name
161+
self.allCookies.removeValue(forKey: key)
162+
updatePersistentStore()
163+
}
164+
}
80165

81166
/*!
82167
@method removeCookiesSince:
83168
@abstract Delete all cookies from the cookie storage since the provided date.
84169
*/
85-
open func removeCookies(since date: Date) { NSUnimplemented() }
86-
170+
open func removeCookies(since date: Date) {
171+
let cookiesSinceDate = allCookies.values.filter {
172+
$0.properties![.created] as! Double > date.timeIntervalSinceReferenceDate
173+
}
174+
for cookie in cookiesSinceDate {
175+
deleteCookie(cookie)
176+
}
177+
updatePersistentStore()
178+
}
179+
87180
/*!
88181
@method cookiesForURL:
89182
@abstract Returns an array of cookies to send to the given URL.
@@ -94,7 +187,17 @@ open class HTTPCookieStorage: NSObject {
94187
<tt>+[NSCookie requestHeaderFieldsWithCookies:]</tt> to turn this array
95188
into a set of header fields to add to a request.
96189
*/
97-
open func cookies(for url: URL) -> [HTTPCookie]? { NSUnimplemented() }
190+
open func cookies(for url: URL) -> [HTTPCookie]? {
191+
var cookies: [HTTPCookie]?
192+
guard let host = url.host else { return nil }
193+
let path = url.path == "" ? "/" : url.path
194+
workQueue.sync {
195+
cookies = Array(allCookies.values.filter {
196+
$0.domain + $0.path == host + path
197+
})
198+
}
199+
return cookies
200+
}
98201

99202
/*!
100203
@method setCookies:forURL:mainDocumentURL:
@@ -113,7 +216,24 @@ open class HTTPCookieStorage: NSObject {
113216
dictionary and then use this method to store the resulting cookies
114217
in accordance with policy settings.
115218
*/
116-
open func setCookies(_ cookies: [HTTPCookie], for url: URL?, mainDocumentURL: URL?) { NSUnimplemented() }
219+
open func setCookies(_ cookies: [HTTPCookie], for url: URL?, mainDocumentURL: URL?) {
220+
guard cookieAcceptPolicy != .never else { return }
221+
guard let theUrl = url else { return }
222+
223+
var validCookies = [HTTPCookie]()
224+
if mainDocumentURL != nil && cookieAcceptPolicy == .onlyFromMainDocumentDomain {
225+
//TODO: needs a careful study of the behaviour on Darwin
226+
NSUnimplemented()
227+
} else {
228+
validCookies = cookies.filter {
229+
theUrl.host != nil && theUrl.host!.hasSuffix($0.domain)
230+
}
231+
}
232+
233+
for cookie in validCookies {
234+
setCookie(cookie)
235+
}
236+
}
117237

118238
/*!
119239
@method cookieAcceptPolicy
@@ -138,3 +258,24 @@ public extension Notification.Name {
138258
*/
139259
public static let NSHTTPCookieManagerCookiesChanged = Notification.Name(rawValue: "NSHTTPCookieManagerCookiesChangedNotification")
140260
}
261+
262+
extension HTTPCookie {
263+
internal func dictionary() -> [String: Any] {
264+
var properties: [String: Any] = [:]
265+
properties[HTTPCookiePropertyKey.name.rawValue] = name
266+
properties[HTTPCookiePropertyKey.path.rawValue] = path
267+
properties[HTTPCookiePropertyKey.value.rawValue] = _value
268+
properties[HTTPCookiePropertyKey.secure.rawValue] = _secure
269+
properties[HTTPCookiePropertyKey.version.rawValue] = _version
270+
properties[HTTPCookiePropertyKey.expires.rawValue] = _expiresDate!.timeIntervalSince1970
271+
properties[HTTPCookiePropertyKey.domain.rawValue] = _domain
272+
if let commentURL = _commentURL {
273+
properties[HTTPCookiePropertyKey.commentURL.rawValue] = commentURL.absoluteString
274+
}
275+
if let comment = _comment {
276+
properties[HTTPCookiePropertyKey.comment.rawValue] = comment
277+
}
278+
properties[HTTPCookiePropertyKey.port.rawValue] = portList
279+
return properties
280+
}
281+
}

0 commit comments

Comments
 (0)