6
6
// See http://swift.org/LICENSE.txt for license information
7
7
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8
8
//
9
-
9
+ import Dispatch
10
10
11
11
/*!
12
12
@enum NSHTTPCookieAcceptPolicy
18
18
*/
19
19
extension HTTPCookie {
20
20
public enum AcceptPolicy : UInt {
21
-
22
21
case always
23
22
case never
24
23
case onlyFromMainDocumentDomain
@@ -35,11 +34,58 @@ extension HTTPCookie {
35
34
generate cookie-related HTTP header fields.
36
35
*/
37
36
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 var cookieFilePath : String !
42
+ private let workQueue : DispatchQueue = DispatchQueue ( label: " HTTPCookieStorage.workqueue " )
43
+ var allCookies : [ String : HTTPCookie ]
44
+
45
+ private init ( cookieStorageName: String ) {
46
+ allCookies = [ : ]
47
+ cookieAcceptPolicy = . always
48
+ super. init ( )
49
+ //TODO: cookieFilePath = filePath(path: _CFXDGCreateConfigHomePath()._swiftObject, fileName: "/.cookies." + cookieStorageName)
50
+ cookieFilePath = filePath ( path: NSHomeDirectory ( ) + " /.config " , fileName: " /.cookies. " + cookieStorageName)
51
+ loadPersistedCookies ( )
52
+ }
53
+
54
+ private func loadPersistedCookies( ) {
55
+ guard let cookies = NSMutableDictionary ( contentsOfFile: cookieFilePath) else { return }
56
+ var cookies0 = _SwiftValue. fetch ( cookies) as? [ String : [ String : Any ] ] ?? [ : ]
57
+ for key in cookies0. keys {
58
+ if let cookie = createCookie ( cookies0 [ key] !) {
59
+ allCookies [ key] = cookie
60
+ }
61
+ }
62
+ }
63
+
64
+ private func directory( with path: String ) -> Bool {
65
+ guard !FileManager. default. fileExists ( atPath: path) else { return true }
66
+
67
+ do {
68
+ try FileManager . default. createDirectory ( atPath: path, withIntermediateDirectories: false , attributes: nil )
69
+ return true
70
+ } catch {
71
+ return false
72
+ }
73
+ }
74
+
75
+ private func filePath( path: String , fileName: String ) -> String {
76
+ if directory ( with: path) {
77
+ return path + fileName
78
+ }
79
+ //if we were unable to create the desired directory, create the cookie file in the `pwd`
80
+ return fileName
81
+ }
82
+
41
83
open var cookies : [ HTTPCookie ] ? {
42
- NSUnimplemented ( )
84
+ var theCookies : [ HTTPCookie ] ?
85
+ workQueue. sync {
86
+ theCookies = Array ( self . allCookies. values)
87
+ }
88
+ return theCookies
43
89
}
44
90
45
91
/*!
@@ -49,7 +95,14 @@ open class HTTPCookieStorage: NSObject {
49
95
@discussion Starting in OS X 10.11, each app has its own sharedHTTPCookieStorage singleton,
50
96
which will not be shared with other applications.
51
97
*/
52
- class var shared : HTTPCookieStorage { get { NSUnimplemented ( ) } }
98
+ open class var shared : HTTPCookieStorage {
99
+ get {
100
+ if sharedStorage == nil {
101
+ sharedStorage = HTTPCookieStorage ( cookieStorageName: " shared " )
102
+ }
103
+ return sharedStorage!
104
+ }
105
+ }
53
106
54
107
/*!
55
108
@method sharedCookieStorageForGroupContainerIdentifier:
@@ -62,28 +115,100 @@ open class HTTPCookieStorage: NSObject {
62
115
shared among all applications and extensions with access to the same application group. Subsequent calls to this
63
116
method with the same identifier will return the same cookie storage instance.
64
117
*/
65
- class func sharedCookieStorage( forGroupContainerIdentifier identifier: String ) -> HTTPCookieStorage { NSUnimplemented ( ) }
118
+ open class func sharedCookieStorage( forGroupContainerIdentifier identifier: String ) -> HTTPCookieStorage {
119
+ guard let cookieStorage = sharedCookieStorages [ identifier] else {
120
+ let newCookieStorage = HTTPCookieStorage ( cookieStorageName: identifier)
121
+ sharedCookieStorages [ identifier] = newCookieStorage
122
+ return newCookieStorage
123
+ }
124
+ return cookieStorage
125
+ }
126
+
66
127
67
128
/*!
68
129
@method setCookie:
69
130
@abstract Set a cookie
70
131
@discussion The cookie will override an existing cookie with the
71
132
same name, domain and path, if any.
72
133
*/
73
- open func setCookie( _ cookie: HTTPCookie ) { NSUnimplemented ( ) }
134
+ open func setCookie( _ cookie: HTTPCookie ) {
135
+ workQueue. sync {
136
+ guard cookieAcceptPolicy != . never else { return }
137
+
138
+ //add or override
139
+ let key = cookie. domain + cookie. path + cookie. name
140
+ if let _ = allCookies. index ( forKey: key) {
141
+ allCookies. updateValue ( cookie, forKey: key)
142
+ } else {
143
+ allCookies [ key] = cookie
144
+ }
145
+
146
+ //remove stale cookies, these may include the one we just added
147
+ let expired = allCookies. filter { ( _, value) in value. expiresDate != nil && value. expiresDate!. timeIntervalSinceNow < 0 }
148
+ for (key, _) in expired {
149
+ self . allCookies. removeValue ( forKey: key)
150
+ }
151
+
152
+ updatePersistentStore ( )
153
+ }
154
+ }
74
155
156
+ private func createCookie( _ properties: [ String : Any ] ) -> HTTPCookie ? {
157
+ var cookieProperties : [ HTTPCookiePropertyKey : Any ] = [ : ]
158
+ for (key, value) in properties {
159
+ if key == " Expires " {
160
+ guard let timestamp = value as? NSNumber else { continue }
161
+ cookieProperties [ HTTPCookiePropertyKey ( rawValue: key) ] = Date ( timeIntervalSince1970: timestamp. doubleValue)
162
+ } else {
163
+ cookieProperties [ HTTPCookiePropertyKey ( rawValue: key) ] = properties [ key]
164
+ }
165
+ }
166
+ return HTTPCookie ( properties: cookieProperties)
167
+ }
168
+
169
+ private func updatePersistentStore( ) {
170
+ //persist cookies
171
+ var persistDictionary : [ String : [ String : Any ] ] = [ : ]
172
+ let persistable = allCookies. filter { ( _, value) in
173
+ value. expiresDate != nil &&
174
+ value. isSessionOnly == false &&
175
+ value. expiresDate!. timeIntervalSinceNow > 0
176
+ }
177
+
178
+ for (key, cookie) in persistable {
179
+ persistDictionary [ key] = cookie. persistableDictionary ( )
180
+ }
181
+
182
+ let nsdict = _SwiftValue. store ( persistDictionary) as! NSDictionary
183
+ _ = nsdict. write ( toFile: cookieFilePath, atomically: true )
184
+ }
185
+
75
186
/*!
76
187
@method deleteCookie:
77
188
@abstract Delete the specified cookie
78
189
*/
79
- open func deleteCookie( _ cookie: HTTPCookie ) { NSUnimplemented ( ) }
190
+ open func deleteCookie( _ cookie: HTTPCookie ) {
191
+ let key = cookie. domain + cookie. path + cookie. name
192
+ workQueue. sync {
193
+ self . allCookies. removeValue ( forKey: key)
194
+ updatePersistentStore ( )
195
+ }
196
+ }
80
197
81
198
/*!
82
199
@method removeCookiesSince:
83
200
@abstract Delete all cookies from the cookie storage since the provided date.
84
201
*/
85
- open func removeCookies( since date: Date ) { NSUnimplemented ( ) }
86
-
202
+ open func removeCookies( since date: Date ) {
203
+ let cookiesSinceDate = allCookies. values. filter {
204
+ $0. properties![ . created] as! Double > date. timeIntervalSinceReferenceDate
205
+ }
206
+ for cookie in cookiesSinceDate {
207
+ deleteCookie ( cookie)
208
+ }
209
+ updatePersistentStore ( )
210
+ }
211
+
87
212
/*!
88
213
@method cookiesForURL:
89
214
@abstract Returns an array of cookies to send to the given URL.
@@ -94,7 +219,14 @@ open class HTTPCookieStorage: NSObject {
94
219
<tt>+[NSCookie requestHeaderFieldsWithCookies:]</tt> to turn this array
95
220
into a set of header fields to add to a request.
96
221
*/
97
- open func cookies( for url: URL ) -> [ HTTPCookie ] ? { NSUnimplemented ( ) }
222
+ open func cookies( for url: URL ) -> [ HTTPCookie ] ? {
223
+ var cookies : [ HTTPCookie ] ?
224
+ guard let host = url. host else { return nil }
225
+ workQueue. sync {
226
+ cookies = Array ( allCookies. values. filter { $0. domain == host } )
227
+ }
228
+ return cookies
229
+ }
98
230
99
231
/*!
100
232
@method setCookies:forURL:mainDocumentURL:
@@ -113,7 +245,26 @@ open class HTTPCookieStorage: NSObject {
113
245
dictionary and then use this method to store the resulting cookies
114
246
in accordance with policy settings.
115
247
*/
116
- open func setCookies( _ cookies: [ HTTPCookie ] , for url: URL ? , mainDocumentURL: URL ? ) { NSUnimplemented ( ) }
248
+ open func setCookies( _ cookies: [ HTTPCookie ] , for url: URL ? , mainDocumentURL: URL ? ) {
249
+ //if the cookieAcceptPolicy is `never` we don't have anything to do
250
+ guard cookieAcceptPolicy != . never else { return }
251
+
252
+ //if the urls don't have a host, we cannot do anything
253
+ guard let urlHost = url? . host else { return }
254
+
255
+ if mainDocumentURL != nil && cookieAcceptPolicy == . onlyFromMainDocumentDomain {
256
+ guard let mainDocumentHost = mainDocumentURL? . host else { return }
257
+
258
+ //the url.host must be a suffix of manDocumentURL.host, this is based on Darwin's behaviour
259
+ guard mainDocumentHost. hasSuffix ( urlHost) else { return }
260
+ }
261
+
262
+ //save only those cookies whose domain matches with the url.host
263
+ let validCookies = cookies. filter { urlHost == $0. domain }
264
+ for cookie in validCookies {
265
+ setCookie ( cookie)
266
+ }
267
+ }
117
268
118
269
/*!
119
270
@method cookieAcceptPolicy
@@ -138,3 +289,24 @@ public extension Notification.Name {
138
289
*/
139
290
public static let NSHTTPCookieManagerCookiesChanged = Notification . Name ( rawValue: " NSHTTPCookieManagerCookiesChangedNotification " )
140
291
}
292
+
293
+ extension HTTPCookie {
294
+ internal func persistableDictionary( ) -> [ String : Any ] {
295
+ var properties : [ String : Any ] = [ : ]
296
+ properties [ HTTPCookiePropertyKey . name. rawValue] = name
297
+ properties [ HTTPCookiePropertyKey . path. rawValue] = path
298
+ properties [ HTTPCookiePropertyKey . value. rawValue] = _value
299
+ properties [ HTTPCookiePropertyKey . secure. rawValue] = _secure
300
+ properties [ HTTPCookiePropertyKey . version. rawValue] = _version
301
+ properties [ HTTPCookiePropertyKey . expires. rawValue] = _expiresDate? . timeIntervalSince1970 ?? Date ( ) . timeIntervalSince1970 //OK?
302
+ properties [ HTTPCookiePropertyKey . domain. rawValue] = _domain
303
+ if let commentURL = _commentURL {
304
+ properties [ HTTPCookiePropertyKey . commentURL. rawValue] = commentURL. absoluteString
305
+ }
306
+ if let comment = _comment {
307
+ properties [ HTTPCookiePropertyKey . comment. rawValue] = comment
308
+ }
309
+ properties [ HTTPCookiePropertyKey . port. rawValue] = portList
310
+ return properties
311
+ }
312
+ }
0 commit comments