@capgo/capacitor-updater 8.51.21 → 8.51.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +18 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +135 -35
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +51 -18
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +103 -28
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +218 -94
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +177 -32
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +9 -1
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +4 -1
- package/package.json +1 -1
|
@@ -134,6 +134,10 @@ import UIKit
|
|
|
134
134
|
case pathTraversal
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
static func containsPathTraversalSegment(_ relativePath: String) -> Bool {
|
|
138
|
+
return relativePath.split(separator: "/").contains(where: { $0 == ".." })
|
|
139
|
+
}
|
|
140
|
+
|
|
137
141
|
static func resolvePathInsideDirectory(baseDirectory: URL, relativePath: String) throws -> URL {
|
|
138
142
|
if relativePath.isEmpty {
|
|
139
143
|
throw SecurePathError.emptyPath
|
|
@@ -141,6 +145,9 @@ import UIKit
|
|
|
141
145
|
if relativePath.contains("\\") || relativePath.contains("\0") {
|
|
142
146
|
throw SecurePathError.windowsPath
|
|
143
147
|
}
|
|
148
|
+
if containsPathTraversalSegment(relativePath) {
|
|
149
|
+
throw SecurePathError.pathTraversal
|
|
150
|
+
}
|
|
144
151
|
if (relativePath as NSString).isAbsolutePath {
|
|
145
152
|
throw SecurePathError.absolutePath
|
|
146
153
|
}
|
|
@@ -151,19 +158,41 @@ import UIKit
|
|
|
151
158
|
let canonicalTarget = canonicalBase.appendingPathComponent(relativePath).standardizedFileURL
|
|
152
159
|
let canonicalTargetPath = canonicalTarget.path
|
|
153
160
|
|
|
154
|
-
|
|
161
|
+
// Require a strict child of the base. Equality would accept "." and wipe/write the root.
|
|
162
|
+
if !canonicalTargetPath.hasPrefix(normalizedBasePath) {
|
|
155
163
|
throw SecurePathError.pathTraversal
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
return canonicalTarget
|
|
159
167
|
}
|
|
160
168
|
|
|
169
|
+
static func resolveBundleDirectory(libraryDir: URL, bundleId: String) throws -> URL {
|
|
170
|
+
let bundleRoot = libraryDir.appendingPathComponent("NoCloud/ionic_built_snapshots")
|
|
171
|
+
return try resolvePathInsideDirectory(baseDirectory: bundleRoot, relativePath: bundleId)
|
|
172
|
+
}
|
|
173
|
+
|
|
161
174
|
static func resolveManifestTargetPath(baseDirectory: URL, fileName: String) throws -> URL {
|
|
162
175
|
let isBrotli = fileName.hasSuffix(".br")
|
|
163
176
|
let targetFileName = isBrotli ? String(fileName.dropLast(3)) : fileName
|
|
164
177
|
return try resolvePathInsideDirectory(baseDirectory: baseDirectory, relativePath: targetFileName)
|
|
165
178
|
}
|
|
166
179
|
|
|
180
|
+
static func rememberManifestTarget(_ seenTargets: inout Set<String>, targetFile: URL) -> Bool {
|
|
181
|
+
return seenTargets.insert(targetFile.standardizedFileURL.path).inserted
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private struct ManifestDownloadTask {
|
|
185
|
+
let fileName: String
|
|
186
|
+
let downloadUrl: String
|
|
187
|
+
let finalFileHash: String
|
|
188
|
+
let isBrotli: Bool
|
|
189
|
+
let destFileName: String
|
|
190
|
+
let destFilePath: URL
|
|
191
|
+
let builtinFilePath: URL
|
|
192
|
+
let cacheFilePath: URL?
|
|
193
|
+
let legacyCacheFilePath: URL?
|
|
194
|
+
}
|
|
195
|
+
|
|
167
196
|
private func isTimedOutError(_ error: Error?) -> Bool {
|
|
168
197
|
guard let nsError = error as NSError? else {
|
|
169
198
|
return false
|
|
@@ -432,6 +461,18 @@ import UIKit
|
|
|
432
461
|
return String((0..<length).map { _ in letters.randomElement()! })
|
|
433
462
|
}
|
|
434
463
|
|
|
464
|
+
private func requireSessionKeyForEncryptedUpdate(sessionKey: String, versionName: String? = nil) throws {
|
|
465
|
+
if !self.publicKey.isEmpty && !CryptoCipher.isValidSessionKey(sessionKey) {
|
|
466
|
+
logger.error("Public key present but no valid session key provided")
|
|
467
|
+
self.sendStats(action: "session_key_required", versionName: versionName)
|
|
468
|
+
throw NSError(
|
|
469
|
+
domain: "CapgoUpdater",
|
|
470
|
+
code: 1,
|
|
471
|
+
userInfo: [NSLocalizedDescriptionKey: "Session key required when public key is present"]
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
435
476
|
public func setPublicKey(_ publicKey: String) {
|
|
436
477
|
// Empty string means no encryption - proceed normally
|
|
437
478
|
if publicKey.isEmpty {
|
|
@@ -963,7 +1004,13 @@ import UIKit
|
|
|
963
1004
|
/// `manifest` must only contain entries the caller already checksum-verified
|
|
964
1005
|
/// (as `downloadManifest` does) — the hashes are trusted as-is, not re-checked.
|
|
965
1006
|
func populateDeltaCache(for id: String, manifest: [ManifestEntry]? = nil, sessionKey: String = "") {
|
|
966
|
-
let bundleDir
|
|
1007
|
+
let bundleDir: URL
|
|
1008
|
+
do {
|
|
1009
|
+
bundleDir = try self.getBundleDirectory(id: id)
|
|
1010
|
+
} catch {
|
|
1011
|
+
logger.debug("Skip delta cache population: invalid bundle id")
|
|
1012
|
+
return
|
|
1013
|
+
}
|
|
967
1014
|
let fileManager = FileManager.default
|
|
968
1015
|
|
|
969
1016
|
guard fileManager.fileExists(atPath: bundleDir.path) else {
|
|
@@ -1223,7 +1270,10 @@ import UIKit
|
|
|
1223
1270
|
guard var fileHash = entry.file_hash, !fileHash.isEmpty else {
|
|
1224
1271
|
return nil
|
|
1225
1272
|
}
|
|
1226
|
-
if !self.publicKey.isEmpty
|
|
1273
|
+
if !self.publicKey.isEmpty {
|
|
1274
|
+
if !CryptoCipher.isValidSessionKey(sessionKey) {
|
|
1275
|
+
return nil
|
|
1276
|
+
}
|
|
1227
1277
|
do {
|
|
1228
1278
|
fileHash = try CryptoCipher.decryptChecksum(checksum: fileHash, publicKey: self.publicKey)
|
|
1229
1279
|
} catch {
|
|
@@ -1384,10 +1434,11 @@ import UIKit
|
|
|
1384
1434
|
}
|
|
1385
1435
|
|
|
1386
1436
|
public func downloadManifest(manifest: [ManifestEntry], version: String, sessionKey: String, link: String? = nil, comment: String? = nil) throws -> BundleInfo {
|
|
1437
|
+
try self.requireSessionKeyForEncryptedUpdate(sessionKey: sessionKey, versionName: version)
|
|
1387
1438
|
try self.runBeforeDownload()
|
|
1388
1439
|
let id = self.randomString(length: 10)
|
|
1389
1440
|
logger.info("downloadManifest start \(id)")
|
|
1390
|
-
let destFolder = self.getBundleDirectory(id: id)
|
|
1441
|
+
let destFolder = try self.getBundleDirectory(id: id)
|
|
1391
1442
|
let builtinFolder = self.builtinFolderURL()
|
|
1392
1443
|
|
|
1393
1444
|
// Check disk space before starting manifest download (estimate 100KB per file, minimum 50MB)
|
|
@@ -1416,8 +1467,8 @@ import UIKit
|
|
|
1416
1467
|
var downloadError: Error?
|
|
1417
1468
|
let errorLock = NSLock()
|
|
1418
1469
|
|
|
1419
|
-
|
|
1420
|
-
var
|
|
1470
|
+
var tasks: [ManifestDownloadTask] = []
|
|
1471
|
+
var seenTargets = Set<String>()
|
|
1421
1472
|
|
|
1422
1473
|
for entry in manifest {
|
|
1423
1474
|
guard let fileName = entry.file_name,
|
|
@@ -1458,7 +1509,23 @@ import UIKit
|
|
|
1458
1509
|
var fileHash = entryFileHash
|
|
1459
1510
|
|
|
1460
1511
|
// Decrypt checksum if needed (done before creating operation)
|
|
1461
|
-
if !self.publicKey.isEmpty
|
|
1512
|
+
if !self.publicKey.isEmpty {
|
|
1513
|
+
if !CryptoCipher.isValidSessionKey(sessionKey) {
|
|
1514
|
+
let error = NSError(
|
|
1515
|
+
domain: "CapgoUpdater",
|
|
1516
|
+
code: 1,
|
|
1517
|
+
userInfo: [NSLocalizedDescriptionKey: "Session key required when public key is present"]
|
|
1518
|
+
)
|
|
1519
|
+
self.sendStats(action: "session_key_required", versionName: version)
|
|
1520
|
+
errorLock.lock()
|
|
1521
|
+
if downloadError == nil {
|
|
1522
|
+
downloadError = error
|
|
1523
|
+
}
|
|
1524
|
+
errorLock.unlock()
|
|
1525
|
+
hasError.value = true
|
|
1526
|
+
logger.error("Public key present but no valid session key provided")
|
|
1527
|
+
continue
|
|
1528
|
+
}
|
|
1462
1529
|
do {
|
|
1463
1530
|
fileHash = try CryptoCipher.decryptChecksum(checksum: fileHash, publicKey: self.publicKey)
|
|
1464
1531
|
} catch {
|
|
@@ -1489,6 +1556,24 @@ import UIKit
|
|
|
1489
1556
|
do {
|
|
1490
1557
|
destFilePath = try Self.resolveManifestTargetPath(baseDirectory: destFolder, fileName: fileName)
|
|
1491
1558
|
builtinFilePath = try Self.resolveManifestTargetPath(baseDirectory: builtinFolder, fileName: fileName)
|
|
1559
|
+
if !Self.rememberManifestTarget(&seenTargets, targetFile: destFilePath) {
|
|
1560
|
+
logger.error("Duplicate manifest target path: \(fileName)")
|
|
1561
|
+
self.sendStats(action: "manifest_path_fail", versionName: "\(version):\(fileName)")
|
|
1562
|
+
let error = NSError(
|
|
1563
|
+
domain: "ManifestEntryError",
|
|
1564
|
+
code: 3,
|
|
1565
|
+
userInfo: [
|
|
1566
|
+
NSLocalizedDescriptionKey: "Duplicate manifest target path for \(fileName)"
|
|
1567
|
+
]
|
|
1568
|
+
)
|
|
1569
|
+
errorLock.lock()
|
|
1570
|
+
if downloadError == nil {
|
|
1571
|
+
downloadError = error
|
|
1572
|
+
}
|
|
1573
|
+
errorLock.unlock()
|
|
1574
|
+
hasError.value = true
|
|
1575
|
+
continue
|
|
1576
|
+
}
|
|
1492
1577
|
} catch {
|
|
1493
1578
|
logger.error("Invalid manifest file path: \(fileName)")
|
|
1494
1579
|
self.sendStats(action: "manifest_path_fail", versionName: "\(version):\(fileName)")
|
|
@@ -1501,8 +1586,40 @@ import UIKit
|
|
|
1501
1586
|
continue
|
|
1502
1587
|
}
|
|
1503
1588
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1589
|
+
tasks.append(
|
|
1590
|
+
ManifestDownloadTask(
|
|
1591
|
+
fileName: fileName,
|
|
1592
|
+
downloadUrl: downloadUrl,
|
|
1593
|
+
finalFileHash: finalFileHash,
|
|
1594
|
+
isBrotli: isBrotli,
|
|
1595
|
+
destFileName: destFileName,
|
|
1596
|
+
destFilePath: destFilePath,
|
|
1597
|
+
builtinFilePath: builtinFilePath,
|
|
1598
|
+
cacheFilePath: cacheFilePath,
|
|
1599
|
+
legacyCacheFilePath: legacyCacheFilePath
|
|
1600
|
+
)
|
|
1601
|
+
)
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
if hasError.value {
|
|
1605
|
+
let resolvedError = downloadError ?? NSError(
|
|
1606
|
+
domain: "ManifestDownloadError",
|
|
1607
|
+
code: 1,
|
|
1608
|
+
userInfo: [NSLocalizedDescriptionKey: "Manifest download failed due to invalid or missing entries"]
|
|
1609
|
+
)
|
|
1610
|
+
let errorBundle = bundleInfo.setStatus(status: BundleStatus.ERROR.storedValue)
|
|
1611
|
+
self.saveBundleInfo(id: id, bundle: errorBundle)
|
|
1612
|
+
throw resolvedError
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
var operations: [Operation] = []
|
|
1616
|
+
|
|
1617
|
+
for task in tasks {
|
|
1618
|
+
try FileManager.default.createDirectory(
|
|
1619
|
+
at: task.destFilePath.deletingLastPathComponent(),
|
|
1620
|
+
withIntermediateDirectories: true,
|
|
1621
|
+
attributes: nil
|
|
1622
|
+
)
|
|
1506
1623
|
|
|
1507
1624
|
let operation = BlockOperation { [weak self] in
|
|
1508
1625
|
guard let self = self else { return }
|
|
@@ -1510,26 +1627,29 @@ import UIKit
|
|
|
1510
1627
|
|
|
1511
1628
|
do {
|
|
1512
1629
|
// Try builtin first
|
|
1513
|
-
if FileManager.default.fileExists(atPath: builtinFilePath.path) &&
|
|
1514
|
-
|
|
1515
|
-
self.
|
|
1630
|
+
if FileManager.default.fileExists(atPath: task.builtinFilePath.path) &&
|
|
1631
|
+
self.verifyChecksum(file: task.builtinFilePath, expectedHash: task.finalFileHash) {
|
|
1632
|
+
try self.copyItemReplacing(from: task.builtinFilePath, to: task.destFilePath)
|
|
1633
|
+
self.logger.info("downloadManifest \(task.fileName) using builtin file \(id)")
|
|
1516
1634
|
}
|
|
1517
1635
|
// Try cache
|
|
1518
1636
|
else if
|
|
1519
|
-
(cacheFilePath != nil &&
|
|
1520
|
-
|
|
1521
|
-
|
|
1637
|
+
(task.cacheFilePath != nil &&
|
|
1638
|
+
self.tryCopyFromCache(from: task.cacheFilePath!, to: task.destFilePath, expectedHash: task.finalFileHash)) ||
|
|
1639
|
+
(task.legacyCacheFilePath != nil &&
|
|
1640
|
+
self.tryCopyFromCache(from: task.legacyCacheFilePath!, to: task.destFilePath, expectedHash: task.finalFileHash)) {
|
|
1641
|
+
self.logger.info("downloadManifest \(task.fileName) copy from cache \(id)")
|
|
1522
1642
|
}
|
|
1523
1643
|
// Download
|
|
1524
1644
|
else {
|
|
1525
1645
|
try self.downloadManifestFile(
|
|
1526
|
-
downloadUrl: downloadUrl,
|
|
1527
|
-
destFilePath: destFilePath,
|
|
1528
|
-
cacheFilePath: cacheFilePath,
|
|
1529
|
-
fileHash: finalFileHash,
|
|
1530
|
-
fileName: fileName,
|
|
1531
|
-
destFileName: destFileName,
|
|
1532
|
-
isBrotli: isBrotli,
|
|
1646
|
+
downloadUrl: task.downloadUrl,
|
|
1647
|
+
destFilePath: task.destFilePath,
|
|
1648
|
+
cacheFilePath: task.cacheFilePath,
|
|
1649
|
+
fileHash: task.finalFileHash,
|
|
1650
|
+
fileName: task.fileName,
|
|
1651
|
+
destFileName: task.destFileName,
|
|
1652
|
+
isBrotli: task.isBrotli,
|
|
1533
1653
|
sessionKey: sessionKey,
|
|
1534
1654
|
version: version,
|
|
1535
1655
|
bundleId: id
|
|
@@ -1547,8 +1667,8 @@ import UIKit
|
|
|
1547
1667
|
}
|
|
1548
1668
|
errorLock.unlock()
|
|
1549
1669
|
hasError.value = true
|
|
1550
|
-
self.logger.error("Manifest file download failed: \(fileName)")
|
|
1551
|
-
self.logger.debug("Bundle: \(id), File: \(fileName), Error: \(error.localizedDescription)")
|
|
1670
|
+
self.logger.error("Manifest file download failed: \(task.fileName)")
|
|
1671
|
+
self.logger.debug("Bundle: \(id), File: \(task.fileName), Error: \(error.localizedDescription)")
|
|
1552
1672
|
}
|
|
1553
1673
|
}
|
|
1554
1674
|
|
|
@@ -1685,7 +1805,7 @@ import UIKit
|
|
|
1685
1805
|
|
|
1686
1806
|
do {
|
|
1687
1807
|
var source = partialURL
|
|
1688
|
-
if !self.publicKey.isEmpty &&
|
|
1808
|
+
if !self.publicKey.isEmpty && CryptoCipher.isValidSessionKey(sessionKey) {
|
|
1689
1809
|
let work = cacheFolder.appendingPathComponent("work_\(UUID().uuidString)_\((fileName as NSString).lastPathComponent)")
|
|
1690
1810
|
try FileManager.default.copyItem(at: partialURL, to: work)
|
|
1691
1811
|
workURL = work
|
|
@@ -1987,6 +2107,7 @@ import UIKit
|
|
|
1987
2107
|
}
|
|
1988
2108
|
|
|
1989
2109
|
public func download(url: URL, version: String, sessionKey: String, link: String? = nil, comment: String? = nil) throws -> BundleInfo {
|
|
2110
|
+
try self.requireSessionKeyForEncryptedUpdate(sessionKey: sessionKey, versionName: version)
|
|
1990
2111
|
try self.runBeforeDownload()
|
|
1991
2112
|
let id: String = self.randomString(length: 10)
|
|
1992
2113
|
// Each download uses its own temp files keyed by bundle ID to prevent collisions
|
|
@@ -2267,6 +2388,15 @@ import UIKit
|
|
|
2267
2388
|
self.deleteLock.lock()
|
|
2268
2389
|
defer { self.deleteLock.unlock() }
|
|
2269
2390
|
|
|
2391
|
+
let destPersist: URL
|
|
2392
|
+
do {
|
|
2393
|
+
destPersist = try self.getBundleDirectory(id: id)
|
|
2394
|
+
} catch {
|
|
2395
|
+
logger.error("Cannot delete bundle with invalid id")
|
|
2396
|
+
logger.debug("Bundle ID: \(id), Error: \(error.localizedDescription)")
|
|
2397
|
+
return false
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2270
2400
|
let deleted: BundleInfo = self.getBundleInfo(id: id)
|
|
2271
2401
|
if deleted.isBuiltin() || self.getCurrentBundleId() == id {
|
|
2272
2402
|
logger.info("Cannot delete current or builtin bundle")
|
|
@@ -2295,7 +2425,6 @@ import UIKit
|
|
|
2295
2425
|
return false
|
|
2296
2426
|
}
|
|
2297
2427
|
|
|
2298
|
-
let destPersist: URL = libraryDir.appendingPathComponent(bundleDirectory).appendingPathComponent(id)
|
|
2299
2428
|
let hadRegistry = self.hasStoredBundleInfo(id: id)
|
|
2300
2429
|
let hadFolder = FileManager.default.fileExists(atPath: destPersist.path)
|
|
2301
2430
|
if !hadRegistry && !hadFolder {
|
|
@@ -2599,8 +2728,8 @@ import UIKit
|
|
|
2599
2728
|
}
|
|
2600
2729
|
}
|
|
2601
2730
|
|
|
2602
|
-
public func getBundleDirectory(id: String) -> URL {
|
|
2603
|
-
return
|
|
2731
|
+
public func getBundleDirectory(id: String) throws -> URL {
|
|
2732
|
+
return try Self.resolveBundleDirectory(libraryDir: libraryDir, bundleId: id)
|
|
2604
2733
|
}
|
|
2605
2734
|
|
|
2606
2735
|
struct ResetState {
|
|
@@ -2654,7 +2783,12 @@ import UIKit
|
|
|
2654
2783
|
}
|
|
2655
2784
|
|
|
2656
2785
|
private func bundleExists(id: String) -> Bool {
|
|
2657
|
-
let destPersist: URL
|
|
2786
|
+
let destPersist: URL
|
|
2787
|
+
do {
|
|
2788
|
+
destPersist = try self.getBundleDirectory(id: id)
|
|
2789
|
+
} catch {
|
|
2790
|
+
return false
|
|
2791
|
+
}
|
|
2658
2792
|
let indexPersist: URL = destPersist.appendingPathComponent("index.html")
|
|
2659
2793
|
let bundleIndo: BundleInfo = self.getBundleInfo(id: id)
|
|
2660
2794
|
if
|
|
@@ -2677,7 +2811,12 @@ import UIKit
|
|
|
2677
2811
|
}
|
|
2678
2812
|
if bundleExists(id: id) {
|
|
2679
2813
|
let currentBundleName = self.getCurrentBundle().getVersionName()
|
|
2680
|
-
|
|
2814
|
+
guard let bundleDir = try? self.getBundleDirectory(id: id) else {
|
|
2815
|
+
self.setBundleStatus(id: id, status: BundleStatus.ERROR)
|
|
2816
|
+
self.sendStats(action: "set_fail", versionName: newBundle.getVersionName())
|
|
2817
|
+
return false
|
|
2818
|
+
}
|
|
2819
|
+
self.setCurrentBundle(bundle: bundleDir.path)
|
|
2681
2820
|
self.setBundleStatus(id: id, status: BundleStatus.PENDING)
|
|
2682
2821
|
self.sendStats(action: "set", versionName: newBundle.getVersionName(), oldVersionName: currentBundleName)
|
|
2683
2822
|
return true
|
|
@@ -2691,7 +2830,10 @@ import UIKit
|
|
|
2691
2830
|
guard !bundle.isBuiltin(), bundleExists(id: bundle.getId()) else {
|
|
2692
2831
|
return false
|
|
2693
2832
|
}
|
|
2694
|
-
|
|
2833
|
+
guard let bundleDir = try? self.getBundleDirectory(id: bundle.getId()) else {
|
|
2834
|
+
return false
|
|
2835
|
+
}
|
|
2836
|
+
self.setCurrentBundle(bundle: bundleDir.path)
|
|
2695
2837
|
return true
|
|
2696
2838
|
}
|
|
2697
2839
|
|
|
@@ -2706,7 +2848,10 @@ import UIKit
|
|
|
2706
2848
|
guard bundleExists(id: bundle.getId()) else {
|
|
2707
2849
|
return false
|
|
2708
2850
|
}
|
|
2709
|
-
|
|
2851
|
+
guard let bundleDir = try? self.getBundleDirectory(id: bundle.getId()) else {
|
|
2852
|
+
return false
|
|
2853
|
+
}
|
|
2854
|
+
self.setCurrentBundle(bundle: bundleDir.path)
|
|
2710
2855
|
return true
|
|
2711
2856
|
}
|
|
2712
2857
|
|
|
@@ -225,8 +225,16 @@ public struct CryptoCipher {
|
|
|
225
225
|
return String(hexString(from: sha256).prefix(16))
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
public static func isValidSessionKey(_ sessionKey: String) -> Bool {
|
|
229
|
+
if sessionKey.isEmpty {
|
|
230
|
+
return false
|
|
231
|
+
}
|
|
232
|
+
let sessionKeyParts = sessionKey.components(separatedBy: ":")
|
|
233
|
+
return sessionKeyParts.count == 2 && !sessionKeyParts[0].isEmpty && !sessionKeyParts[1].isEmpty
|
|
234
|
+
}
|
|
235
|
+
|
|
228
236
|
public static func decryptFile(filePath: URL, publicKey: String, sessionKey: String, version: String) throws {
|
|
229
|
-
if publicKey.isEmpty || sessionKey
|
|
237
|
+
if publicKey.isEmpty || !isValidSessionKey(sessionKey) {
|
|
230
238
|
logger.info("Encryption not set, no public key or session, ignored")
|
|
231
239
|
return
|
|
232
240
|
}
|
|
@@ -359,7 +359,10 @@ extension UIWindow {
|
|
|
359
359
|
if !next.isBuiltin() {
|
|
360
360
|
plugin.logger.info("Resetting to: \(next.toString())")
|
|
361
361
|
_ = updater.set(bundle: next)
|
|
362
|
-
let destHot = updater.getBundleDirectory(id: next.getId())
|
|
362
|
+
guard let destHot = try? updater.getBundleDirectory(id: next.getId()) else {
|
|
363
|
+
resetBuiltin()
|
|
364
|
+
return
|
|
365
|
+
}
|
|
363
366
|
plugin.logger.info("Reloading \(next.toString())")
|
|
364
367
|
bridge.setServerBasePath(destHot.path)
|
|
365
368
|
} else {
|