-
Notifications
You must be signed in to change notification settings - Fork 875
Expand file tree
/
Copy pathCookieHeaderCache.swift
More file actions
137 lines (123 loc) · 5.49 KB
/
CookieHeaderCache.swift
File metadata and controls
137 lines (123 loc) · 5.49 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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import Foundation
public enum CookieHeaderCache {
public struct Entry: Codable, Sendable {
public let cookieHeader: String
public let storedAt: Date
public let sourceLabel: String
public init(cookieHeader: String, storedAt: Date, sourceLabel: String) {
self.cookieHeader = cookieHeader
self.storedAt = storedAt
self.sourceLabel = sourceLabel
}
}
private static let log = CodexBarLog.logger(LogCategories.cookieCache)
private nonisolated(unsafe) static var legacyBaseURLOverride: URL?
public static func load(provider: UsageProvider) -> Entry? {
let key = KeychainCacheStore.Key.cookie(provider: provider)
switch KeychainCacheStore.load(key: key, as: Entry.self) {
case let .found(entry):
self.log.debug("Cookie cache hit", metadata: ["provider": provider.rawValue])
return entry
case .invalid:
self.log.warning("Cookie cache invalid; clearing", metadata: ["provider": provider.rawValue])
KeychainCacheStore.clear(key: key)
case .missing:
self.log.debug("Cookie cache miss", metadata: ["provider": provider.rawValue])
}
guard let legacy = self.loadLegacyEntry(for: provider) else { return nil }
KeychainCacheStore.store(key: key, entry: legacy)
self.removeLegacyEntry(for: provider)
self.log.debug("Cookie cache migrated from legacy store", metadata: ["provider": provider.rawValue])
return legacy
}
public static func store(
provider: UsageProvider,
cookieHeader: String,
sourceLabel: String,
now: Date = Date())
{
let trimmed = cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines)
guard let normalized = CookieHeaderNormalizer.normalize(trimmed), !normalized.isEmpty else {
self.clear(provider: provider)
return
}
let entry = Entry(cookieHeader: normalized, storedAt: now, sourceLabel: sourceLabel)
let key = KeychainCacheStore.Key.cookie(provider: provider)
KeychainCacheStore.store(key: key, entry: entry)
self.removeLegacyEntry(for: provider)
self.log.debug("Cookie cache stored", metadata: ["provider": provider.rawValue, "source": sourceLabel])
}
public static func clear(provider: UsageProvider) {
let key = KeychainCacheStore.Key.cookie(provider: provider)
KeychainCacheStore.clear(key: key)
self.removeLegacyEntry(for: provider)
self.log.debug("Cookie cache cleared", metadata: ["provider": provider.rawValue])
}
/// Clears cookie caches for all providers, including corrupt/invalid entries.
/// Returns the number of providers whose caches were cleared.
@discardableResult
public static func clearAll() -> Int {
var cleared = 0
for provider in UsageProvider.allCases {
let key = KeychainCacheStore.Key.cookie(provider: provider)
let result = KeychainCacheStore.load(key: key, as: Entry.self)
let hasLegacy = self.loadLegacyEntry(for: provider) != nil
switch result {
case .found, .invalid:
self.clear(provider: provider)
cleared += 1
case .missing where hasLegacy:
self.removeLegacyEntry(for: provider)
cleared += 1
case .missing:
break
}
}
self.log.debug("Cookie cache clearAll completed", metadata: ["cleared": "\(cleared)"])
return cleared
}
static func load(from url: URL) -> Entry? {
guard let data = try? Data(contentsOf: url) else { return nil }
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return try? decoder.decode(Entry.self, from: data)
}
static func store(_ entry: Entry, to url: URL) {
do {
let dir = url.deletingLastPathComponent()
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let data = try encoder.encode(entry)
try data.write(to: url, options: [.atomic])
} catch {
self.log.error("Failed to persist cookie cache: \(error)")
}
}
static func setLegacyBaseURLOverrideForTesting(_ url: URL?) {
self.legacyBaseURLOverride = url
}
private static func loadLegacyEntry(for provider: UsageProvider) -> Entry? {
self.load(from: self.legacyURL(for: provider))
}
private static func removeLegacyEntry(for provider: UsageProvider) {
let url = self.legacyURL(for: provider)
do {
try FileManager.default.removeItem(at: url)
} catch {
if (error as NSError).code != NSFileNoSuchFileError {
Self.log.error("Failed to remove cookie cache (\(provider.rawValue)): \(error)")
}
}
}
private static func legacyURL(for provider: UsageProvider) -> URL {
if let override = self.legacyBaseURLOverride {
return override.appendingPathComponent("\(provider.rawValue)-cookie.json")
}
let fm = FileManager.default
let base = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
?? fm.temporaryDirectory
return base.appendingPathComponent("CodexBar", isDirectory: true)
.appendingPathComponent("\(provider.rawValue)-cookie.json")
}
}