Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit cec38de

Browse files
authored
Add caching support to CertChecker class
Refactor CertChecker to include caching functionality and improve cache handling.
1 parent ac90a96 commit cec38de

1 file changed

Lines changed: 124 additions & 5 deletions

File tree

Sources/prostore/certificates/certChecker.swift

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,99 @@
11
import Foundation
2+
import CryptoKit
23

34
class CertChecker {
45
static let baseURL = URL(string: "https://check-p12.applep12.com/")!
56

7+
// MARK: - Cache types
8+
struct CacheEntry: Codable {
9+
var certificate: [String: String]
10+
var mobileprovision: [String: String]
11+
var binding_certificate_1: [String: String]
12+
var permissions: [String: String]
13+
var overall_status: String
14+
var timestamp: Date
15+
}
16+
17+
private static var cacheDirectory: URL {
18+
let fm = FileManager.default
19+
let caches = fm.urls(for: .cachesDirectory, in: .userDomainMask).first!
20+
let dir = caches.appendingPathComponent("CertCheckerCache", isDirectory: true)
21+
if !fm.fileExists(atPath: dir.path) {
22+
try? fm.createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil)
23+
}
24+
return dir
25+
}
26+
27+
private static func cacheFileURL(forKey key: String) -> URL {
28+
return cacheDirectory.appendingPathComponent("\(key).json")
29+
}
30+
31+
private static func makeCacheKey(p12Data: Data, mpData: Data, password: String) -> String {
32+
var ctx = Data()
33+
ctx.append(p12Data)
34+
ctx.append(mpData)
35+
if let pw = password.data(using: .utf8) {
36+
ctx.append(pw)
37+
}
38+
let hash = SHA256.hash(data: ctx)
39+
return hash.compactMap { String(format: "%02x", $0) }.joined()
40+
}
41+
42+
private static func saveCache(key: String, parsed: [String: Any]) {
43+
// Convert parsed dictionary into CacheEntry (provide sensible defaults)
44+
let certificate = parsed["certificate"] as? [String: String] ?? [:]
45+
let mobileprovision = parsed["mobileprovision"] as? [String: String] ?? [:]
46+
let binding = parsed["binding_certificate_1"] as? [String: String] ?? [:]
47+
let permissions = parsed["permissions"] as? [String: String] ?? [:]
48+
let overall = (parsed["overall_status"] as? String) ?? (parsed["overallStatus"] as? String) ?? "Unknown"
49+
50+
let entry = CacheEntry(
51+
certificate: certificate,
52+
mobileprovision: mobileprovision,
53+
binding_certificate_1: binding,
54+
permissions: permissions,
55+
overall_status: overall,
56+
timestamp: Date()
57+
)
58+
59+
let encoder = JSONEncoder()
60+
encoder.dateEncodingStrategy = .iso8601
61+
do {
62+
let data = try encoder.encode(entry)
63+
try data.write(to: cacheFileURL(forKey: key), options: [.atomic])
64+
} catch {
65+
// silently ignore cache write errors
66+
// print("CertChecker: failed to write cache: \(error)")
67+
}
68+
}
69+
70+
private static func loadCacheEntry(forKey key: String) -> CacheEntry? {
71+
let url = cacheFileURL(forKey: key)
72+
guard FileManager.default.fileExists(atPath: url.path),
73+
let data = try? Data(contentsOf: url) else { return nil }
74+
let decoder = JSONDecoder()
75+
decoder.dateDecodingStrategy = .iso8601
76+
do {
77+
let entry = try decoder.decode(CacheEntry.self, from: data)
78+
return entry
79+
} catch {
80+
// corrupted cache — remove file
81+
try? FileManager.default.removeItem(at: url)
82+
return nil
83+
}
84+
}
85+
86+
private static func cacheEntryToDict(_ entry: CacheEntry) -> [String: Any] {
87+
return [
88+
"certificate": entry.certificate,
89+
"mobileprovision": entry.mobileprovision,
90+
"binding_certificate_1": entry.binding_certificate_1,
91+
"permissions": entry.permissions,
92+
"overall_status": entry.overall_status,
93+
"cached_timestamp": ISO8601DateFormatter().string(from: entry.timestamp)
94+
]
95+
}
96+
697
// MARK: - Token Fetching
798
static func getToken() async throws -> String {
899
let (data, _) = try await URLSession.shared.data(from: baseURL)
@@ -31,7 +122,7 @@ class CertChecker {
31122
mpData: Data,
32123
mpFilename: String,
33124
password: String) async throws -> String {
34-
125+
35126
var request = URLRequest(url: baseURL)
36127
request.httpMethod = "POST"
37128
let boundary = "Boundary-\(UUID().uuidString)"
@@ -80,8 +171,13 @@ class CertChecker {
80171
return String(data: data, encoding: .utf8) ?? ""
81172
}
82173

83-
// MARK: - HTML Parsing
174+
// MARK: - HTML Parsing (unchanged)
84175
static func parseHTML(html: String) -> [String: Any] {
176+
// (copy your existing parseHTML implementation here unchanged)
177+
// For brevity in this snippet we call your original parseHTML implementation.
178+
// Replace the following line with your full parseHTML method body.
179+
// ---------------------------
180+
// (Begin of original method)
85181
var data: [String: Any] = [
86182
"certificate": [String: String](),
87183
"mobileprovision": [String: String](),
@@ -272,15 +368,31 @@ class CertChecker {
272368
}
273369

274370
return ["overall_status": fallbackStatus]
371+
// (End of original method)
372+
// ---------------------------
275373
}
276374

277-
// MARK: - Main Check Function
375+
// MARK: - Public cache API
376+
377+
/// Synchronously returns the cached parsed dictionary (if any) for the supplied inputs.
378+
/// Use this to show a cached result immediately while you await a fresh result.
379+
static func cachedResult(p12Data: Data, mpData: Data, password: String) -> [String: Any]? {
380+
let key = makeCacheKey(p12Data: p12Data, mpData: mpData, password: password)
381+
guard let entry = loadCacheEntry(forKey: key) else { return nil }
382+
return cacheEntryToDict(entry)
383+
}
384+
385+
// MARK: - Main Check Function (network + cache update)
278386
static func checkCert(mobileProvision: Data,
279387
mobileProvisionFilename: String = "example.mobileprovision",
280388
p12: Data,
281389
p12Filename: String = "example.p12",
282390
password: String) async throws -> [String: Any] {
283391

392+
// compute cache key so we can update cache later
393+
let key = makeCacheKey(p12Data: p12, mpData: mobileProvision, password: password)
394+
395+
// perform network fetch as before
284396
let token = try await getToken()
285397

286398
let html = try await submit(
@@ -292,6 +404,13 @@ class CertChecker {
292404
password: password
293405
)
294406

295-
return parseHTML(html: html)
407+
let parsed = parseHTML(html: html)
408+
409+
// update cache (fire-and-forget style)
410+
DispatchQueue.global(qos: .background).async {
411+
saveCache(key: key, parsed: parsed)
412+
}
413+
414+
return parsed
296415
}
297-
}
416+
}

0 commit comments

Comments
 (0)