Skip to content

Commit abce6a3

Browse files
author
Pushkar Kulkarni
committed
Initial implementation of HTTPCookieStorage
1 parent a30b185 commit abce6a3

File tree

4 files changed

+387
-14
lines changed

4 files changed

+387
-14
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: 166 additions & 14 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,38 @@ 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+
private static var sharedCookieStorages: [String: HTTPCookieStorage] = [:] //for group storage containers
40+
41+
private let cookieFilePath: String
42+
private let workQueue: DispatchQueue = DispatchQueue(label: "HTTPCookieStorage.workqueue")
43+
var allCookies: [String: HTTPCookie]
44+
45+
private init(cookieStorageName: String) {
46+
cookieFilePath = NSHomeDirectory() + "/.cookies-" + cookieStorageName
47+
allCookies = [:]
48+
cookieAcceptPolicy = .always
49+
super.init()
50+
loadPersistedCookies()
51+
}
52+
53+
private func loadPersistedCookies() {
54+
guard let cookies = NSMutableDictionary(contentsOfFile: cookieFilePath) else { return }
55+
var cookies0 = _SwiftValue.fetch(cookies) as? [String: [String: Any]] ?? [:]
56+
for key in cookies0.keys {
57+
if let cookie = createCookie(cookies0[key]!) {
58+
allCookies[key] = cookie
59+
}
60+
}
61+
}
62+
4163
open var cookies: [HTTPCookie]? {
42-
NSUnimplemented()
64+
var theCookies: [HTTPCookie]?
65+
workQueue.sync {
66+
theCookies = Array(self.allCookies.values)
67+
}
68+
return theCookies
4369
}
4470

4571
/*!
@@ -49,7 +75,14 @@ open class HTTPCookieStorage: NSObject {
4975
@discussion Starting in OS X 10.11, each app has its own sharedHTTPCookieStorage singleton,
5076
which will not be shared with other applications.
5177
*/
52-
class var shared: HTTPCookieStorage { get { NSUnimplemented() } }
78+
open class var shared: HTTPCookieStorage {
79+
get {
80+
if sharedStorage == nil {
81+
sharedStorage = HTTPCookieStorage(cookieStorageName: "shared")
82+
}
83+
return sharedStorage!
84+
}
85+
}
5386

5487
/*!
5588
@method sharedCookieStorageForGroupContainerIdentifier:
@@ -62,28 +95,100 @@ open class HTTPCookieStorage: NSObject {
6295
shared among all applications and extensions with access to the same application group. Subsequent calls to this
6396
method with the same identifier will return the same cookie storage instance.
6497
*/
65-
class func sharedCookieStorage(forGroupContainerIdentifier identifier: String) -> HTTPCookieStorage { NSUnimplemented() }
98+
open class func sharedCookieStorage(forGroupContainerIdentifier identifier: String) -> HTTPCookieStorage {
99+
guard let cookieStorage = sharedCookieStorages[identifier] else {
100+
let newCookieStorage = HTTPCookieStorage(cookieStorageName: identifier)
101+
sharedCookieStorages[identifier] = newCookieStorage
102+
return newCookieStorage
103+
}
104+
return cookieStorage
105+
}
106+
66107

67108
/*!
68109
@method setCookie:
69110
@abstract Set a cookie
70111
@discussion The cookie will override an existing cookie with the
71112
same name, domain and path, if any.
72113
*/
73-
open func setCookie(_ cookie: HTTPCookie) { NSUnimplemented() }
114+
open func setCookie(_ cookie: HTTPCookie) {
115+
workQueue.sync {
116+
guard cookieAcceptPolicy != .never else { return }
117+
118+
//add or override
119+
let key = cookie.domain + cookie.path + cookie.name
120+
if let _ = allCookies.index(forKey: key) {
121+
allCookies.updateValue(cookie, forKey: key)
122+
} else {
123+
allCookies[key] = cookie
124+
}
125+
126+
//remove stale cookies, these may include the one we just added
127+
let expired = allCookies.filter { (_, value) in value.expiresDate != nil && value.expiresDate!.timeIntervalSinceNow < 0 }
128+
for (key,_) in expired {
129+
self.allCookies.removeValue(forKey: key)
130+
}
131+
132+
updatePersistentStore()
133+
}
134+
}
74135

136+
private func createCookie(_ properties: [String: Any]) -> HTTPCookie? {
137+
var cookieProperties: [HTTPCookiePropertyKey: Any] = [:]
138+
for (key, value) in properties {
139+
if key == "Expires" {
140+
guard let timestamp = value as? NSNumber else { continue }
141+
cookieProperties[HTTPCookiePropertyKey(rawValue: key)] = Date(timeIntervalSince1970: timestamp.doubleValue)
142+
} else {
143+
cookieProperties[HTTPCookiePropertyKey(rawValue: key)] = properties[key]
144+
}
145+
}
146+
return HTTPCookie(properties: cookieProperties)
147+
}
148+
149+
private func updatePersistentStore() {
150+
//persist cookies
151+
var persistDictionary: [String : [String : Any]] = [:]
152+
let persistable = allCookies.filter { (_, value) in
153+
value.expiresDate != nil &&
154+
value.isSessionOnly == false &&
155+
value.expiresDate!.timeIntervalSinceNow > 0
156+
}
157+
158+
for (key,cookie) in persistable {
159+
persistDictionary[key] = cookie.persistableDictionary()
160+
}
161+
162+
let nsdict = _SwiftValue.store(persistDictionary) as! NSDictionary
163+
_ = nsdict.write(toFile: cookieFilePath, atomically: true)
164+
}
165+
75166
/*!
76167
@method deleteCookie:
77168
@abstract Delete the specified cookie
78169
*/
79-
open func deleteCookie(_ cookie: HTTPCookie) { NSUnimplemented() }
170+
open func deleteCookie(_ cookie: HTTPCookie) {
171+
let key = cookie.domain + cookie.path + cookie.name
172+
workQueue.sync {
173+
self.allCookies.removeValue(forKey: key)
174+
updatePersistentStore()
175+
}
176+
}
80177

81178
/*!
82179
@method removeCookiesSince:
83180
@abstract Delete all cookies from the cookie storage since the provided date.
84181
*/
85-
open func removeCookies(since date: Date) { NSUnimplemented() }
86-
182+
open func removeCookies(since date: Date) {
183+
let cookiesSinceDate = allCookies.values.filter {
184+
$0.properties![.created] as! Double > date.timeIntervalSinceReferenceDate
185+
}
186+
for cookie in cookiesSinceDate {
187+
deleteCookie(cookie)
188+
}
189+
updatePersistentStore()
190+
}
191+
87192
/*!
88193
@method cookiesForURL:
89194
@abstract Returns an array of cookies to send to the given URL.
@@ -94,7 +199,14 @@ open class HTTPCookieStorage: NSObject {
94199
<tt>+[NSCookie requestHeaderFieldsWithCookies:]</tt> to turn this array
95200
into a set of header fields to add to a request.
96201
*/
97-
open func cookies(for url: URL) -> [HTTPCookie]? { NSUnimplemented() }
202+
open func cookies(for url: URL) -> [HTTPCookie]? {
203+
var cookies: [HTTPCookie]?
204+
guard let host = url.host else { return nil }
205+
workQueue.sync {
206+
cookies = Array(allCookies.values.filter{ $0.domain == host })
207+
}
208+
return cookies
209+
}
98210

99211
/*!
100212
@method setCookies:forURL:mainDocumentURL:
@@ -113,7 +225,26 @@ open class HTTPCookieStorage: NSObject {
113225
dictionary and then use this method to store the resulting cookies
114226
in accordance with policy settings.
115227
*/
116-
open func setCookies(_ cookies: [HTTPCookie], for url: URL?, mainDocumentURL: URL?) { NSUnimplemented() }
228+
open func setCookies(_ cookies: [HTTPCookie], for url: URL?, mainDocumentURL: URL?) {
229+
//if the cookieAcceptPolicy is `never` we don't have anything to do
230+
guard cookieAcceptPolicy != .never else { return }
231+
232+
//if the urls don't have a host, we cannot do anything
233+
guard let urlHost = url?.host else { return }
234+
235+
if mainDocumentURL != nil && cookieAcceptPolicy == .onlyFromMainDocumentDomain {
236+
guard let mainDocumentHost = mainDocumentURL?.host else { return }
237+
238+
//the url.host must be a suffix of manDocumentURL.host, this is based on Darwin's behaviour
239+
guard mainDocumentHost.hasSuffix(urlHost) else { return }
240+
}
241+
242+
//save only those cookies whose domain matches with the url.host
243+
let validCookies = cookies.filter { urlHost == $0.domain }
244+
for cookie in validCookies {
245+
setCookie(cookie)
246+
}
247+
}
117248

118249
/*!
119250
@method cookieAcceptPolicy
@@ -138,3 +269,24 @@ public extension Notification.Name {
138269
*/
139270
public static let NSHTTPCookieManagerCookiesChanged = Notification.Name(rawValue: "NSHTTPCookieManagerCookiesChangedNotification")
140271
}
272+
273+
extension HTTPCookie {
274+
internal func persistableDictionary() -> [String: Any] {
275+
var properties: [String: Any] = [:]
276+
properties[HTTPCookiePropertyKey.name.rawValue] = name
277+
properties[HTTPCookiePropertyKey.path.rawValue] = path
278+
properties[HTTPCookiePropertyKey.value.rawValue] = _value
279+
properties[HTTPCookiePropertyKey.secure.rawValue] = _secure
280+
properties[HTTPCookiePropertyKey.version.rawValue] = _version
281+
properties[HTTPCookiePropertyKey.expires.rawValue] = _expiresDate?.timeIntervalSince1970 ?? Date().timeIntervalSince1970 //OK?
282+
properties[HTTPCookiePropertyKey.domain.rawValue] = _domain
283+
if let commentURL = _commentURL {
284+
properties[HTTPCookiePropertyKey.commentURL.rawValue] = commentURL.absoluteString
285+
}
286+
if let comment = _comment {
287+
properties[HTTPCookiePropertyKey.comment.rawValue] = comment
288+
}
289+
properties[HTTPCookiePropertyKey.port.rawValue] = portList
290+
return properties
291+
}
292+
}

0 commit comments

Comments
 (0)