@hot-updater/react-native 1.0.0-rc.2 → 1.0.0-rc.21

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.
Files changed (291) hide show
  1. package/android/proguard-rules.pro +17 -2
  2. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  3. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +954 -837
  4. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +15 -11
  5. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  6. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  7. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +43 -31
  8. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
  9. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  10. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  11. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  12. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  13. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  14. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  15. package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
  16. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  17. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  18. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  19. package/android/src/newarch/HotUpdater.kt +7 -9
  20. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  21. package/android/src/oldarch/HotUpdater.kt +7 -9
  22. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  23. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  24. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  25. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  26. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +950 -1112
  27. package/ios/HotUpdater/Internal/BundleMetadata.swift +8 -53
  28. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  29. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  30. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  31. package/ios/HotUpdater/Internal/HotUpdater.mm +13 -45
  32. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +190 -73
  33. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  34. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  35. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  36. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  37. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  38. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  39. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  40. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  41. package/lib/commonjs/appReady.js +111 -0
  42. package/lib/commonjs/appReady.js.map +1 -0
  43. package/lib/commonjs/checkForUpdate.js +125 -70
  44. package/lib/commonjs/checkForUpdate.js.map +1 -1
  45. package/lib/commonjs/clientPlugin.js +13 -0
  46. package/lib/commonjs/clientPlugin.js.map +1 -0
  47. package/lib/commonjs/error.js +4 -13
  48. package/lib/commonjs/error.js.map +1 -1
  49. package/lib/commonjs/fetchJSON.js +5 -2
  50. package/lib/commonjs/fetchJSON.js.map +1 -1
  51. package/lib/commonjs/httpClient.js +55 -92
  52. package/lib/commonjs/httpClient.js.map +1 -1
  53. package/lib/commonjs/index.js +68 -20
  54. package/lib/commonjs/index.js.map +1 -1
  55. package/lib/commonjs/native.js +82 -62
  56. package/lib/commonjs/native.js.map +1 -1
  57. package/lib/commonjs/pluginHost.js +44 -0
  58. package/lib/commonjs/pluginHost.js.map +1 -0
  59. package/lib/commonjs/releaseCatalogCache.js +20 -61
  60. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  61. package/lib/commonjs/sdkVersion.js +1 -1
  62. package/lib/commonjs/sdkVersion.js.map +1 -1
  63. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  64. package/lib/commonjs/store.js +3 -10
  65. package/lib/commonjs/store.js.map +1 -1
  66. package/lib/commonjs/updateError.js +132 -0
  67. package/lib/commonjs/updateError.js.map +1 -0
  68. package/lib/commonjs/wrap.js +44 -27
  69. package/lib/commonjs/wrap.js.map +1 -1
  70. package/lib/module/appReady.js +103 -0
  71. package/lib/module/appReady.js.map +1 -0
  72. package/lib/module/checkForUpdate.js +118 -65
  73. package/lib/module/checkForUpdate.js.map +1 -1
  74. package/lib/module/clientPlugin.js +4 -0
  75. package/lib/module/clientPlugin.js.map +1 -0
  76. package/lib/module/error.js +4 -13
  77. package/lib/module/error.js.map +1 -1
  78. package/lib/module/fetchJSON.js +3 -1
  79. package/lib/module/fetchJSON.js.map +1 -1
  80. package/lib/module/httpClient.js +53 -90
  81. package/lib/module/httpClient.js.map +1 -1
  82. package/lib/module/index.js +55 -22
  83. package/lib/module/index.js.map +1 -1
  84. package/lib/module/native.js +74 -54
  85. package/lib/module/native.js.map +1 -1
  86. package/lib/module/pluginHost.js +35 -0
  87. package/lib/module/pluginHost.js.map +1 -0
  88. package/lib/module/releaseCatalogCache.js +16 -56
  89. package/lib/module/releaseCatalogCache.js.map +1 -1
  90. package/lib/module/sdkVersion.js +1 -1
  91. package/lib/module/sdkVersion.js.map +1 -1
  92. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  93. package/lib/module/store.js +3 -10
  94. package/lib/module/store.js.map +1 -1
  95. package/lib/module/updateError.js +126 -0
  96. package/lib/module/updateError.js.map +1 -0
  97. package/lib/module/wrap.js +45 -28
  98. package/lib/module/wrap.js.map +1 -1
  99. package/lib/typescript/commonjs/appReady.d.ts +23 -0
  100. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  102. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  104. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/error.d.ts +4 -13
  106. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/fetchJSON.d.ts +3 -0
  108. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/httpClient.d.ts +3 -32
  110. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/index.d.ts +7 -12
  112. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/native.d.ts +33 -23
  114. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  116. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +6 -14
  118. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  120. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +42 -47
  122. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/store.d.ts +1 -3
  124. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/types.d.ts +1 -1
  126. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  128. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  131. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  132. package/lib/typescript/module/appReady.d.ts +23 -0
  133. package/lib/typescript/module/appReady.d.ts.map +1 -0
  134. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  135. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  136. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  137. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  138. package/lib/typescript/module/error.d.ts +4 -13
  139. package/lib/typescript/module/error.d.ts.map +1 -1
  140. package/lib/typescript/module/fetchJSON.d.ts +3 -0
  141. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  142. package/lib/typescript/module/httpClient.d.ts +3 -32
  143. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  144. package/lib/typescript/module/index.d.ts +7 -12
  145. package/lib/typescript/module/index.d.ts.map +1 -1
  146. package/lib/typescript/module/native.d.ts +33 -23
  147. package/lib/typescript/module/native.d.ts.map +1 -1
  148. package/lib/typescript/module/pluginHost.d.ts +4 -0
  149. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  150. package/lib/typescript/module/releaseCatalogCache.d.ts +6 -14
  151. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  152. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  153. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  154. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +42 -47
  155. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  156. package/lib/typescript/module/store.d.ts +1 -3
  157. package/lib/typescript/module/store.d.ts.map +1 -1
  158. package/lib/typescript/module/types.d.ts +1 -1
  159. package/lib/typescript/module/types.d.ts.map +1 -1
  160. package/lib/typescript/module/updateError.d.ts +26 -0
  161. package/lib/typescript/module/updateError.d.ts.map +1 -0
  162. package/lib/typescript/module/wrap.d.ts.map +1 -1
  163. package/lib/typescript/module/wrap.types.d.ts +6 -7
  164. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  165. package/package.json +7 -4
  166. package/src/appReady.ts +149 -0
  167. package/src/checkForUpdate.ts +152 -89
  168. package/src/clientPlugin.ts +17 -0
  169. package/src/error.ts +4 -14
  170. package/src/fetchJSON.ts +5 -1
  171. package/src/httpClient.ts +78 -157
  172. package/src/index.ts +87 -29
  173. package/src/native.ts +109 -104
  174. package/src/pluginHost.ts +45 -0
  175. package/src/releaseCatalogCache.ts +26 -140
  176. package/src/sdkVersion.ts +1 -1
  177. package/src/specs/NativeHotUpdater.ts +41 -48
  178. package/src/store.ts +2 -23
  179. package/src/types.ts +1 -1
  180. package/src/updateError.ts +153 -0
  181. package/src/wrap.tsx +59 -28
  182. package/src/wrap.types.ts +6 -7
  183. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  184. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  185. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  186. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  187. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  188. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  189. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  190. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  191. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  192. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  193. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  194. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  195. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  196. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  197. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  198. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  199. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  200. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  201. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  202. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  203. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  204. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  205. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  206. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -604
  207. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  208. package/lib/commonjs/httpClient.spec.js +0 -102
  209. package/lib/commonjs/httpClient.spec.js.map +0 -1
  210. package/lib/commonjs/index.spec.js +0 -298
  211. package/lib/commonjs/index.spec.js.map +0 -1
  212. package/lib/commonjs/iosInterop.spec.js +0 -18
  213. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  214. package/lib/commonjs/native.spec.js +0 -795
  215. package/lib/commonjs/native.spec.js.map +0 -1
  216. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  217. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  218. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  219. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  220. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  221. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  222. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  223. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  224. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  225. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  226. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  227. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  228. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  229. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  230. package/lib/commonjs/store.notification.spec.js +0 -76
  231. package/lib/commonjs/store.notification.spec.js.map +0 -1
  232. package/lib/commonjs/store.spec.js +0 -316
  233. package/lib/commonjs/store.spec.js.map +0 -1
  234. package/lib/commonjs/wrap.spec.js +0 -296
  235. package/lib/commonjs/wrap.spec.js.map +0 -1
  236. package/lib/module/catalogCacheNative.spec.js +0 -31
  237. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  238. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -603
  239. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  240. package/lib/module/httpClient.spec.js +0 -102
  241. package/lib/module/httpClient.spec.js.map +0 -1
  242. package/lib/module/index.spec.js +0 -297
  243. package/lib/module/index.spec.js.map +0 -1
  244. package/lib/module/iosInterop.spec.js +0 -18
  245. package/lib/module/iosInterop.spec.js.map +0 -1
  246. package/lib/module/native.spec.js +0 -794
  247. package/lib/module/native.spec.js.map +0 -1
  248. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  249. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  250. package/lib/module/notifyAppReadyInsights.js +0 -128
  251. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  252. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  253. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  254. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  255. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  256. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  257. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  258. package/lib/module/releaseCatalogCache.spec.js +0 -195
  259. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  260. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  261. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  262. package/lib/module/store.notification.spec.js +0 -77
  263. package/lib/module/store.notification.spec.js.map +0 -1
  264. package/lib/module/store.spec.js +0 -315
  265. package/lib/module/store.spec.js.map +0 -1
  266. package/lib/module/wrap.spec.js +0 -295
  267. package/lib/module/wrap.spec.js.map +0 -1
  268. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  269. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  270. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  271. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  272. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  273. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  274. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  275. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  276. package/src/catalogCacheNative.spec.ts +0 -63
  277. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -596
  278. package/src/httpClient.spec.ts +0 -132
  279. package/src/index.spec.ts +0 -331
  280. package/src/iosInterop.spec.ts +0 -30
  281. package/src/native.spec.ts +0 -855
  282. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  283. package/src/notifyAppReadyInsights.spec.ts +0 -243
  284. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  285. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  286. package/src/notifyAppReadyInsights.ts +0 -194
  287. package/src/releaseCatalogCache.spec.ts +0 -282
  288. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  289. package/src/store.notification.spec.ts +0 -92
  290. package/src/store.spec.ts +0 -372
  291. package/src/wrap.spec.ts +0 -367
@@ -2,385 +2,256 @@ import Foundation
2
2
 
3
3
  enum TarArchiveExtractor {
4
4
  private static let blockSize = 512
5
-
6
5
  private static let regularFileType: UInt8 = 48
7
6
  private static let alternateRegularFileType: UInt8 = 0
8
- private static let hardLinkType: UInt8 = 49
9
- private static let symbolicLinkType: UInt8 = 50
10
- private static let directoryType: UInt8 = 53
11
- private static let contiguousFileType: UInt8 = 55
12
- private static let globalPaxHeaderType: UInt8 = 103
13
7
  private static let paxHeaderType: UInt8 = 120
14
- private static let gnuLongNameType: UInt8 = 76
15
- private static let gnuLongLinkType: UInt8 = 75
8
+ private static let maximumPaxByteSize = 1024 * 1024
16
9
 
17
10
  private struct Header {
18
11
  let path: String
19
12
  let size: UInt64
20
13
  let typeFlag: UInt8
21
- let linkName: String
22
- }
23
-
24
- static func containsEntries(at tarPath: String) throws -> Bool {
25
- let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: tarPath))
26
-
27
- defer {
28
- try? handle.close()
29
- }
30
-
31
- var globalPaxHeaders: [String: String] = [:]
32
- var pendingPaxHeaders: [String: String] = [:]
33
- var pendingLongPath: String?
34
- var pendingLongLink: String?
35
-
36
- while true {
37
- let headerBlock = try ArchiveExtractionUtilities.readExactly(from: handle, count: blockSize)
38
- guard !isZeroBlock(headerBlock) else {
39
- return false
40
- }
41
-
42
- let header = try parseHeader(from: headerBlock)
43
-
44
- switch header.typeFlag {
45
- case globalPaxHeaderType:
46
- let paxData = try readEntryPayloadData(from: handle, size: header.size)
47
- globalPaxHeaders.merge(parsePaxHeaders(from: paxData)) { _, newValue in
48
- newValue
49
- }
50
-
51
- case paxHeaderType:
52
- let paxData = try readEntryPayloadData(from: handle, size: header.size)
53
- pendingPaxHeaders.merge(parsePaxHeaders(from: paxData)) { _, newValue in
54
- newValue
55
- }
56
-
57
- case gnuLongNameType:
58
- pendingLongPath = decodeLongPath(from: try readEntryPayloadData(from: handle, size: header.size))
59
-
60
- case gnuLongLinkType:
61
- pendingLongLink = decodeLongPath(from: try readEntryPayloadData(from: handle, size: header.size))
62
-
63
- default:
64
- let effectiveHeaders = globalPaxHeaders.merging(pendingPaxHeaders) { _, newValue in
65
- newValue
66
- }
67
- let resolvedPath = pendingLongPath ?? effectiveHeaders["path"] ?? header.path
68
- _ = pendingLongLink ?? effectiveHeaders["linkpath"] ?? header.linkName
69
-
70
- defer {
71
- pendingPaxHeaders.removeAll()
72
- pendingLongPath = nil
73
- pendingLongLink = nil
74
- }
75
-
76
- if let normalizedPath = ArchiveExtractionUtilities.normalizedRelativePath(from: resolvedPath),
77
- !normalizedPath.isEmpty {
78
- return true
79
- }
80
-
81
- try skipEntryPayload(in: handle, size: header.size)
82
- }
83
- }
84
14
  }
85
15
 
86
16
  static func extract(
87
17
  from tarPath: String,
88
18
  to destination: String,
89
- progressHandler: @escaping (Double) -> Void
19
+ expectedFiles: [String: Int64]
90
20
  ) throws {
91
- let fileManager = FileManager.default
92
- let destinationRoot = URL(fileURLWithPath: destination).standardizedFileURL.path
93
- try ArchiveExtractionUtilities.ensureDirectory(at: URL(fileURLWithPath: destinationRoot), fileManager: fileManager)
21
+ let destinationURL = URL(fileURLWithPath: destination, isDirectory: true)
22
+ try FileManager.default.createDirectory(
23
+ at: destinationURL,
24
+ withIntermediateDirectories: true
25
+ )
94
26
 
95
- let tarSize = try archiveFileSize(at: tarPath)
96
27
  let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: tarPath))
28
+ defer { try? handle.close() }
97
29
 
98
- defer {
99
- try? handle.close()
100
- }
101
-
102
- var globalPaxHeaders: [String: String] = [:]
103
- var pendingPaxHeaders: [String: String] = [:]
104
- var pendingLongPath: String?
105
- var pendingLongLink: String?
30
+ var extractedPaths = Set<String>()
31
+ var pendingPaxHeaders: [String: String]?
106
32
 
107
33
  while true {
108
- let headerBlock = try ArchiveExtractionUtilities.readExactly(from: handle, count: blockSize)
109
- guard !isZeroBlock(headerBlock) else {
110
- break
111
- }
112
-
113
- let header = try parseHeader(from: headerBlock)
114
-
115
- switch header.typeFlag {
116
- case globalPaxHeaderType:
117
- let paxData = try readEntryPayloadData(from: handle, size: header.size)
118
- globalPaxHeaders.merge(parsePaxHeaders(from: paxData)) { _, newValue in
119
- newValue
34
+ let headerBlock = try readExactly(from: handle, count: blockSize)
35
+ if isZeroBlock(headerBlock) {
36
+ let secondEndBlock = try readExactly(from: handle, count: blockSize)
37
+ guard isZeroBlock(secondEndBlock), pendingPaxHeaders == nil else {
38
+ throw archiveError(1, "Invalid TAR termination")
120
39
  }
121
-
122
- case paxHeaderType:
123
- let paxData = try readEntryPayloadData(from: handle, size: header.size)
124
- pendingPaxHeaders.merge(parsePaxHeaders(from: paxData)) { _, newValue in
125
- newValue
40
+ try requireZeroPaddingToEnd(handle)
41
+ guard extractedPaths == Set(expectedFiles.keys) else {
42
+ throw archiveError(2, "TAR file set does not match the manifest")
126
43
  }
44
+ return
45
+ }
127
46
 
128
- case gnuLongNameType:
129
- pendingLongPath = decodeLongPath(from: try readEntryPayloadData(from: handle, size: header.size))
47
+ let header = try parseHeader(headerBlock)
48
+ if header.typeFlag == paxHeaderType {
49
+ guard pendingPaxHeaders == nil,
50
+ header.size <= UInt64(maximumPaxByteSize) else {
51
+ throw archiveError(3, "Invalid PAX header")
52
+ }
53
+ let payload = try readPayload(from: handle, size: header.size)
54
+ pendingPaxHeaders = try parsePaxHeaders(payload)
55
+ continue
56
+ }
130
57
 
131
- case gnuLongLinkType:
132
- pendingLongLink = decodeLongPath(from: try readEntryPayloadData(from: handle, size: header.size))
58
+ guard header.typeFlag == regularFileType ||
59
+ header.typeFlag == alternateRegularFileType else {
60
+ throw archiveError(4, "Unsupported TAR entry type")
61
+ }
133
62
 
134
- default:
135
- let effectiveHeaders = globalPaxHeaders.merging(pendingPaxHeaders) { _, newValue in
136
- newValue
137
- }
138
- let resolvedPath = pendingLongPath ?? effectiveHeaders["path"] ?? header.path
139
- let resolvedLinkPath = pendingLongLink ?? effectiveHeaders["linkpath"] ?? header.linkName
63
+ let paxHeaders = pendingPaxHeaders ?? [:]
64
+ pendingPaxHeaders = nil
65
+ guard paxHeaders["linkpath"] == nil else {
66
+ throw archiveError(5, "TAR links are not allowed")
67
+ }
68
+ let rawPath = paxHeaders["path"] ?? header.path
69
+ guard FileUtilities.normalizedRelativePath(from: rawPath) == rawPath,
70
+ rawPath != "manifest.json",
71
+ !extractedPaths.contains(rawPath),
72
+ let expectedByteSize = expectedFiles[rawPath],
73
+ expectedByteSize >= 0 else {
74
+ throw archiveError(6, "TAR contains an unsafe, duplicate, or unknown path")
75
+ }
140
76
 
141
- defer {
142
- pendingPaxHeaders.removeAll()
143
- pendingLongPath = nil
144
- pendingLongLink = nil
77
+ let entrySize: UInt64
78
+ if let paxSize = paxHeaders["size"] {
79
+ guard let parsedSize = UInt64(paxSize) else {
80
+ throw archiveError(7, "Invalid PAX entry size")
145
81
  }
82
+ entrySize = parsedSize
83
+ } else {
84
+ entrySize = header.size
85
+ }
86
+ guard entrySize == UInt64(expectedByteSize) else {
87
+ throw archiveError(8, "TAR entry size does not match the manifest")
88
+ }
146
89
 
147
- try extractEntry(
148
- path: resolvedPath,
149
- typeFlag: header.typeFlag,
150
- size: header.size,
151
- linkPath: resolvedLinkPath,
90
+ let outputURL = try FileUtilities.fileURL(
91
+ for: rawPath,
92
+ destinationRoot: destination
93
+ )
94
+ try FileManager.default.createDirectory(
95
+ at: outputURL.deletingLastPathComponent(),
96
+ withIntermediateDirectories: true
97
+ )
98
+ guard FileManager.default.createFile(atPath: outputURL.path, contents: nil) else {
99
+ throw archiveError(9, "Failed to create TAR output file")
100
+ }
101
+ let output = try FileHandle(forWritingTo: outputURL)
102
+ do {
103
+ try copyPayload(
152
104
  from: handle,
153
- to: destinationRoot
105
+ size: entrySize,
106
+ to: output
154
107
  )
108
+ try output.close()
109
+ } catch {
110
+ try? output.close()
111
+ try? FileManager.default.removeItem(at: outputURL)
112
+ throw error
155
113
  }
156
-
157
- if tarSize > 0 {
158
- let offset = ArchiveExtractionUtilities.currentOffset(for: handle)
159
- let progress = min(Double(offset) / Double(tarSize), 1.0)
160
- progressHandler(progress)
161
- }
114
+ try readPadding(from: handle, size: entrySize)
115
+ extractedPaths.insert(rawPath)
162
116
  }
163
-
164
- progressHandler(1.0)
165
117
  }
166
118
 
167
- private static func extractEntry(
168
- path rawPath: String,
169
- typeFlag: UInt8,
170
- size: UInt64,
171
- linkPath: String,
172
- from handle: FileHandle,
173
- to destinationRoot: String
174
- ) throws {
175
- guard let relativePath = ArchiveExtractionUtilities.normalizedRelativePath(from: rawPath) else {
176
- try skipEntryPayload(in: handle, size: size)
177
- return
119
+ private static func parseHeader(_ block: Data) throws -> Header {
120
+ guard block.count == blockSize,
121
+ Data(block[257..<262]) == Data("ustar".utf8),
122
+ try verifyChecksum(block) else {
123
+ throw archiveError(10, "Invalid USTAR header")
178
124
  }
179
-
180
- let targetURL = try ArchiveExtractionUtilities.extractionURL(
181
- for: relativePath,
182
- destinationRoot: destinationRoot
125
+ let name = try parseString(block[0..<100])
126
+ let prefix = try parseString(block[345..<500])
127
+ let path = prefix.isEmpty ? name : (name.isEmpty ? prefix : "\(prefix)/\(name)")
128
+ return Header(
129
+ path: path,
130
+ size: try parseOctal(block[124..<136]),
131
+ typeFlag: block[156]
183
132
  )
184
-
185
- switch typeFlag {
186
- case directoryType:
187
- try ArchiveExtractionUtilities.ensureDirectory(at: targetURL)
188
- try skipEntryPayload(in: handle, size: size)
189
-
190
- case regularFileType, alternateRegularFileType, contiguousFileType:
191
- let outputHandle = try ArchiveExtractionUtilities.createOutputFile(at: targetURL)
192
-
193
- defer {
194
- try? outputHandle.close()
195
- }
196
-
197
- try copyEntryPayload(from: handle, size: size, to: outputHandle)
198
- try skipPadding(in: handle, size: size)
199
-
200
- case hardLinkType, symbolicLinkType:
201
- NSLog("[TarArchiveExtractor] Skipping link entry: \(rawPath) -> \(linkPath)")
202
- try skipEntryPayload(in: handle, size: size)
203
-
204
- default:
205
- NSLog("[TarArchiveExtractor] Skipping unsupported TAR entry type: \(typeFlag) (\(rawPath))")
206
- try skipEntryPayload(in: handle, size: size)
207
- }
208
133
  }
209
134
 
210
- private static func copyEntryPayload(
211
- from handle: FileHandle,
212
- size: UInt64,
213
- to outputHandle: FileHandle
214
- ) throws {
215
- var remainingBytes = size
216
-
217
- while remainingBytes > 0 {
218
- let chunkSize = Int(min(remainingBytes, UInt64(ArchiveExtractionUtilities.bufferSize)))
219
- let chunk = try ArchiveExtractionUtilities.readExactly(from: handle, count: chunkSize)
220
- outputHandle.write(chunk)
221
- remainingBytes -= UInt64(chunk.count)
135
+ private static func verifyChecksum(_ block: Data) throws -> Bool {
136
+ let expected = try parseOctal(block[148..<156])
137
+ var actual: UInt64 = 0
138
+ for index in block.indices {
139
+ actual += UInt64((148..<156).contains(index) ? 32 : block[index])
222
140
  }
141
+ return actual == expected
223
142
  }
224
143
 
225
- private static func readEntryPayloadData(from handle: FileHandle, size: UInt64) throws -> Data {
226
- guard size > 0 else {
227
- return Data()
144
+ private static func parseOctal(_ bytes: Data.SubSequence) throws -> UInt64 {
145
+ let value = try parseString(bytes)
146
+ .trimmingCharacters(in: CharacterSet(charactersIn: " \0"))
147
+ if value.isEmpty { return 0 }
148
+ guard value.allSatisfy({ ("0"..."7").contains($0) }),
149
+ let parsed = UInt64(value, radix: 8) else {
150
+ throw archiveError(11, "Invalid TAR numeric field")
228
151
  }
229
-
230
- guard size <= UInt64(Int.max) else {
231
- throw NSError(
232
- domain: "TarArchiveExtractor",
233
- code: 3,
234
- userInfo: [NSLocalizedDescriptionKey: "TAR payload exceeds supported in-memory size: \(size) bytes"]
235
- )
236
- }
237
-
238
- let payload = try ArchiveExtractionUtilities.readExactly(from: handle, count: Int(size))
239
- try skipPadding(in: handle, size: size)
240
- return payload
241
- }
242
-
243
- private static func skipEntryPayload(in handle: FileHandle, size: UInt64) throws {
244
- try ArchiveExtractionUtilities.skipBytes(size, in: handle)
245
- try skipPadding(in: handle, size: size)
246
- }
247
-
248
- private static func skipPadding(in handle: FileHandle, size: UInt64) throws {
249
- let padding = (UInt64(blockSize) - (size % UInt64(blockSize))) % UInt64(blockSize)
250
- try ArchiveExtractionUtilities.skipBytes(padding, in: handle)
152
+ return parsed
251
153
  }
252
154
 
253
- private static func parseHeader(from block: Data) throws -> Header {
254
- guard block.count == blockSize else {
255
- throw NSError(
256
- domain: "TarArchiveExtractor",
257
- code: 1,
258
- userInfo: [NSLocalizedDescriptionKey: "Invalid TAR block size: \(block.count)"]
259
- )
155
+ private static func parseString(_ bytes: Data.SubSequence) throws -> String {
156
+ let value = bytes.prefix { $0 != 0 }
157
+ guard let decoded = String(data: Data(value), encoding: .utf8) else {
158
+ throw archiveError(12, "Invalid UTF-8 in TAR header")
260
159
  }
261
-
262
- return Header(
263
- path: parseTarPath(from: block),
264
- size: try parseTarNumber(block[124..<136]),
265
- typeFlag: block[156],
266
- linkName: parseCString(block[157..<257])
267
- )
160
+ return decoded
268
161
  }
269
162
 
270
- private static func parseTarPath(from block: Data) -> String {
271
- let name = parseCString(block[0..<100])
272
- let prefix = parseCString(block[345..<500])
273
-
274
- guard !prefix.isEmpty else {
275
- return name
276
- }
277
-
278
- guard !name.isEmpty else {
279
- return prefix
163
+ private static func parsePaxHeaders(_ data: Data) throws -> [String: String] {
164
+ var headers: [String: String] = [:]
165
+ var offset = 0
166
+ while offset < data.count {
167
+ guard let space = data[offset...].firstIndex(of: 0x20),
168
+ let lengthText = String(data: data[offset..<space], encoding: .ascii),
169
+ let recordLength = Int(lengthText),
170
+ recordLength > space - offset + 2 else {
171
+ throw archiveError(13, "Malformed PAX record")
172
+ }
173
+ let (end, overflowed) = offset.addingReportingOverflow(recordLength)
174
+ guard !overflowed, end <= data.count, data[end - 1] == 0x0A else {
175
+ throw archiveError(13, "Malformed PAX record")
176
+ }
177
+ let bodyStart = space + 1
178
+ guard let equals = data[bodyStart..<(end - 1)].firstIndex(of: 0x3D),
179
+ let key = String(data: data[bodyStart..<equals], encoding: .utf8),
180
+ let value = String(data: data[(equals + 1)..<(end - 1)], encoding: .utf8),
181
+ !key.isEmpty,
182
+ headers[key] == nil else {
183
+ throw archiveError(13, "Malformed PAX record")
184
+ }
185
+ headers[key] = value
186
+ offset = end
280
187
  }
281
-
282
- return "\(prefix)/\(name)"
188
+ return headers
283
189
  }
284
190
 
285
- private static func parseCString(_ data: Data.SubSequence) -> String {
286
- let bytes = data.prefix { $0 != 0 }
287
- guard !bytes.isEmpty else {
288
- return ""
289
- }
290
-
291
- if let decoded = String(data: Data(bytes), encoding: .utf8) {
292
- return decoded
191
+ private static func copyPayload(
192
+ from input: FileHandle,
193
+ size: UInt64,
194
+ to output: FileHandle
195
+ ) throws {
196
+ var remaining = size
197
+ while remaining > 0 {
198
+ let count = Int(min(remaining, 64 * 1024))
199
+ let chunk = try readExactly(from: input, count: count)
200
+ output.write(chunk)
201
+ remaining -= UInt64(chunk.count)
293
202
  }
294
-
295
- return String(decoding: bytes, as: UTF8.self)
296
203
  }
297
204
 
298
- private static func parseTarNumber(_ data: Data.SubSequence) throws -> UInt64 {
299
- let bytes = [UInt8](data)
300
- guard !bytes.allSatisfy({ $0 == 0 || $0 == 32 }) else {
301
- return 0
205
+ private static func readPayload(from handle: FileHandle, size: UInt64) throws -> Data {
206
+ guard size <= UInt64(Int.max) else {
207
+ throw archiveError(14, "TAR payload is too large")
302
208
  }
209
+ let payload = try readExactly(from: handle, count: Int(size))
210
+ try readPadding(from: handle, size: size)
211
+ return payload
212
+ }
303
213
 
304
- if let first = bytes.first, first & 0x80 != 0 {
305
- var value: UInt64 = UInt64(first & 0x7F)
306
- for byte in bytes.dropFirst() {
307
- value = (value << 8) | UInt64(byte)
214
+ private static func readPadding(from handle: FileHandle, size: UInt64) throws {
215
+ let padding = (UInt64(blockSize) - size % UInt64(blockSize)) % UInt64(blockSize)
216
+ if padding > 0 {
217
+ let bytes = try readExactly(from: handle, count: Int(padding))
218
+ guard bytes.allSatisfy({ $0 == 0 }) else {
219
+ throw archiveError(15, "Invalid TAR entry padding")
308
220
  }
309
- return value
310
221
  }
311
-
312
- let stringValue = String(bytes: bytes, encoding: .ascii)?
313
- .trimmingCharacters(in: CharacterSet(charactersIn: "\0 "))
314
-
315
- guard let stringValue, !stringValue.isEmpty,
316
- let parsedValue = UInt64(stringValue, radix: 8) else {
317
- throw NSError(
318
- domain: "TarArchiveExtractor",
319
- code: 2,
320
- userInfo: [NSLocalizedDescriptionKey: "Invalid TAR numeric field"]
321
- )
322
- }
323
-
324
- return parsedValue
325
222
  }
326
223
 
327
- private static func parsePaxHeaders(from data: Data) -> [String: String] {
328
- var headers: [String: String] = [:]
329
- var index = data.startIndex
330
-
331
- while index < data.endIndex {
332
- guard let spaceIndex = data[index...].firstIndex(of: 0x20),
333
- let lengthString = String(data: data[index..<spaceIndex], encoding: .ascii),
334
- let recordLength = Int(lengthString),
335
- recordLength > 0 else {
336
- break
337
- }
338
-
339
- let recordEnd = index + recordLength
340
- guard recordEnd <= data.endIndex else {
341
- break
342
- }
343
-
344
- let recordBodyStart = data.index(after: spaceIndex)
345
- let recordBody = data[recordBodyStart..<recordEnd]
346
-
347
- if let newlineIndex = recordBody.lastIndex(of: 0x0A),
348
- let separatorIndex = recordBody[..<newlineIndex].firstIndex(of: 0x3D),
349
- let key = String(data: recordBody[..<separatorIndex], encoding: .utf8),
350
- let value = String(data: recordBody[recordBody.index(after: separatorIndex)..<newlineIndex], encoding: .utf8) {
351
- headers[key] = value
224
+ private static func requireZeroPaddingToEnd(_ handle: FileHandle) throws {
225
+ while true {
226
+ let bytes = try FileUtilities.readUpToCount(
227
+ from: handle,
228
+ count: blockSize
229
+ ) ?? Data()
230
+ if bytes.isEmpty { return }
231
+ guard bytes.count == blockSize,
232
+ bytes.allSatisfy({ $0 == 0 }) else {
233
+ throw archiveError(16, "TAR contains trailing data")
352
234
  }
353
-
354
- index = recordEnd
355
235
  }
356
-
357
- return headers
358
236
  }
359
237
 
360
- private static func decodeLongPath(from data: Data) -> String {
361
- let trimmedData = data.prefix { $0 != 0 }
362
- guard !trimmedData.isEmpty else {
363
- return ""
238
+ private static func readExactly(from handle: FileHandle, count: Int) throws -> Data {
239
+ guard let data = try FileUtilities.readUpToCount(from: handle, count: count),
240
+ data.count == count else {
241
+ throw archiveError(17, "Truncated TAR archive")
364
242
  }
365
-
366
- return String(decoding: trimmedData, as: UTF8.self)
367
- .trimmingCharacters(in: .newlines)
243
+ return data
368
244
  }
369
245
 
370
246
  private static func isZeroBlock(_ data: Data) -> Bool {
371
247
  data.allSatisfy { $0 == 0 }
372
248
  }
373
249
 
374
- private static func archiveFileSize(at path: String) throws -> UInt64 {
375
- let attributes = try FileManager.default.attributesOfItem(atPath: path)
376
- if let number = attributes[.size] as? NSNumber {
377
- return number.uint64Value
378
- }
379
-
380
- if let value = attributes[.size] as? UInt64 {
381
- return value
382
- }
383
-
384
- return 0
250
+ private static func archiveError(_ code: Int, _ message: String) -> NSError {
251
+ NSError(
252
+ domain: "TarArchiveExtractor",
253
+ code: code,
254
+ userInfo: [NSLocalizedDescriptionKey: message]
255
+ )
385
256
  }
386
257
  }