11import Foundation
2+ import CryptoKit
23
34class 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