-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSessionCookieStore.swift
More file actions
102 lines (88 loc) · 3.85 KB
/
Copy pathSessionCookieStore.swift
File metadata and controls
102 lines (88 loc) · 3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import Foundation
public class SessionCookieStore {
// MARK: - Private properties
private static let bundleIdentifier = Bundle.main.bundleIdentifier ?? "com.mendix.app"
private static let storageKey = bundleIdentifier + "sessionCookies"
private static let queue = DispatchQueue(label: bundleIdentifier + ".session-cookie-store", qos: .utility)
// MARK: - Public API
public static func restore() {
guard let cookies = get(key: storageKey) else {
NSLog("SessionCookieStore: No cookies to restore")
return
}
let storage = HTTPCookieStorage.shared
let existing = Set(storage.cookies ?? [])
cookies.filter { !existing.contains($0) }.forEach { storage.setCookie($0) }
clear()
}
public static func persist() {
queue.async {
let sessionCookies = HTTPCookieStorage.shared.cookies?.filter { isSessionCookie($0) } ?? []
guard !sessionCookies.isEmpty else {
clear()
NSLog("SessionCookieStore: Clear existing session cookies from storage")
return
}
set(key: storageKey, cookies: sessionCookies)
}
}
public static func clear() {
clear(key: storageKey)
}
// MARK: - Private API
private static func isSessionCookie(_ cookie: HTTPCookie) -> Bool {
return cookie.expiresDate == nil
}
private static func set(key: String, cookies: [HTTPCookie]) {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: cookies, requiringSecureCoding: false)
clear(key: key)
let storeQuery = [kSecClass: kSecClassGenericPassword, kSecAttrAccount: key, kSecValueData: data] as CFDictionary
let status = SecItemAdd(storeQuery, nil)
if status != noErr {
NSLog("SessionCookieStore: Failed to persist session cookies with status: \(status)")
}
} catch {
NSLog("SessionCookieStore: Failed to persist session cookies: \(error.localizedDescription)")
}
}
private static func get(key: String) -> [HTTPCookie]? {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: key,
kSecReturnData: true,
kSecUseAuthenticationUI: kSecUseAuthenticationUIFail
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
if status == errSecInteractionNotAllowed {
NSLog("SessionCookieStore: Item inaccessible (device locked), skipping restore")
return nil
} else if status == errSecSuccess, let data = item as? Data {
let result = ObjCExceptionCatcher.catchException {
try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, HTTPCookie.self], from: data)
}
guard let cookies = result as? [HTTPCookie] else {
NSLog("SessionCookieStore: Failed to deserialize cookies, clearing")
clear(key: key)
return nil
}
return cookies
} else if status != errSecItemNotFound {
// Any other unreadable state — delete to prevent repeated failures
NSLog("SessionCookieStore: Unreadable legacy item (status: \(status)), clearing")
clear(key: key)
return nil
} else {
NSLog("SessionCookieStore: No session cookies found")
return nil
}
}
private static func clear(key: String) {
let query = [kSecClass: kSecClassGenericPassword, kSecAttrAccount: key] as CFDictionary
let status = SecItemDelete(query)
if status != errSecSuccess && status != errSecItemNotFound {
NSLog("SessionCookieStore: Failed to clear cookies with status: \(status)")
}
}
}