Skip to content

Commit 783820a

Browse files
author
Pushkar Kulkarni
committed
Initial implementation of HTTPCookieStorage
1 parent af83051 commit 783820a

File tree

4 files changed

+305
-12
lines changed

4 files changed

+305
-12
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 */; };
@@ -457,6 +458,7 @@
457458
/* Begin PBXFileReference section */
458459
0383A1741D2E558A0052E5D1 /* TestNSStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSStream.swift; sourceTree = "<group>"; };
459460
1520469A1D8AEABE00D02E36 /* HTTPServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPServer.swift; sourceTree = "<group>"; };
461+
159884911DCC877700E3314C /* TestNSHTTPCookieStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestNSHTTPCookieStorage.swift; sourceTree = "<group>"; };
460462
22B9C1E01C165D7A00DECFF9 /* TestNSDate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSDate.swift; sourceTree = "<group>"; };
461463
231503DA1D8AEE5D0061694D /* TestNSDecimal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSDecimal.swift; sourceTree = "<group>"; };
462464
294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSAttributedString.swift; sourceTree = "<group>"; };
@@ -1318,6 +1320,7 @@
13181320
EA66F65A1BF1976100136161 /* Tests */ = {
13191321
isa = PBXGroup;
13201322
children = (
1323+
159884911DCC877700E3314C /* TestNSHTTPCookieStorage.swift */,
13211324
D4FE895A1D703D1100DA7986 /* TestURLRequest.swift */,
13221325
C93559281C12C49F009FD6A9 /* TestNSAffineTransform.swift */,
13231326
EA66F63C1BF1619600136161 /* TestNSArray.swift */,
@@ -2204,6 +2207,7 @@
22042207
isa = PBXSourcesBuildPhase;
22052208
buildActionMask = 2147483647;
22062209
files = (
2210+
159884921DCC877700E3314C /* TestNSHTTPCookieStorage.swift in Sources */,
22072211
5FE52C951D147D1C00F7D270 /* TestNSTextCheckingResult.swift in Sources */,
22082212
5B13B3451C582D4C00651CE2 /* TestNSString.swift in Sources */,
22092213
1520469B1D8AEABE00D02E36 /* HTTPServer.swift in Sources */,

Foundation/NSHTTPCookieStorage.swift

Lines changed: 153 additions & 12 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,19 +101,81 @@ 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() }
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+
}
86179

87180
/*!
88181
@method cookiesForURL:
@@ -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
@@ -129,6 +249,7 @@ open class HTTPCookieStorage: NSObject {
129249
@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.
130250
*/
131251
open func sortedCookies(using sortOrder: [SortDescriptor]) -> [HTTPCookie] { NSUnimplemented() }
252+
132253
}
133254

134255
/*!
@@ -137,3 +258,23 @@ open class HTTPCookieStorage: NSObject {
137258
*/
138259
public let NSHTTPCookieManagerCookiesChangedNotification: String = "" // NSUnimplemented
139260

261+
extension HTTPCookie {
262+
internal func dictionary() -> [String: Any] {
263+
var properties: [String: Any] = [:]
264+
properties[HTTPCookiePropertyKey.name.rawValue] = name
265+
properties[HTTPCookiePropertyKey.path.rawValue] = path
266+
properties[HTTPCookiePropertyKey.value.rawValue] = _value
267+
properties[HTTPCookiePropertyKey.secure.rawValue] = _secure
268+
properties[HTTPCookiePropertyKey.version.rawValue] = _version
269+
properties[HTTPCookiePropertyKey.expires.rawValue] = _expiresDate!.timeIntervalSince1970
270+
properties[HTTPCookiePropertyKey.domain.rawValue] = _domain
271+
if let commentURL = _commentURL {
272+
properties[HTTPCookiePropertyKey.commentURL.rawValue] = commentURL.absoluteString
273+
}
274+
if let comment = _comment {
275+
properties[HTTPCookiePropertyKey.comment.rawValue] = comment
276+
}
277+
properties[HTTPCookiePropertyKey.port.rawValue] = portList
278+
return properties
279+
}
280+
}

0 commit comments

Comments
 (0)