@onekeyfe/react-native-range-downloader 3.0.81 → 3.0.82

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 (146) hide show
  1. package/ReactNativeRangeDownloader.podspec +7 -0
  2. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRules.kt +291 -0
  3. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweep.kt +111 -0
  4. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactStore.kt +822 -0
  5. package/android/src/main/java/com/margelo/nitro/reactnativerangedownloader/ReactNativeRangeDownloader.kt +148 -0
  6. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveRulesTest.kt +114 -0
  7. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactDeadlineTest.kt +26 -0
  8. package/android/src/test/java/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactOrphanSweepTest.kt +107 -0
  9. package/ios/FirmwareArchiveMinizipBridge.h +33 -0
  10. package/ios/FirmwareArchiveMinizipBridge.mm +213 -0
  11. package/ios/FirmwareArtifactStore.swift +1508 -0
  12. package/ios/RangeDownloadLogic.swift +317 -0
  13. package/ios/ReactNativeRangeDownloader.swift +214 -6
  14. package/lib/module/index.js +2 -1
  15. package/lib/module/index.js.map +1 -1
  16. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts +92 -1
  17. package/lib/typescript/src/ReactNativeRangeDownloader.nitro.d.ts.map +1 -1
  18. package/lib/typescript/src/index.d.ts +1 -0
  19. package/lib/typescript/src/index.d.ts.map +1 -1
  20. package/nitrogen/generated/android/c++/JDownloadChannel.hpp +8 -6
  21. package/nitrogen/generated/android/c++/JFirmwareArchiveExpectedEntry.hpp +69 -0
  22. package/nitrogen/generated/android/c++/JFirmwareArchiveMaterializeParams.hpp +87 -0
  23. package/nitrogen/generated/android/c++/JFirmwareArchiveMaterializeResult.hpp +80 -0
  24. package/nitrogen/generated/android/c++/JFirmwareArchiveMaterializedArtifact.hpp +63 -0
  25. package/nitrogen/generated/android/c++/JFirmwareArtifactCancelParams.hpp +57 -0
  26. package/nitrogen/generated/android/c++/JFirmwareArtifactCapabilities.hpp +88 -0
  27. package/nitrogen/generated/android/c++/JFirmwareArtifactDownloadParams.hpp +98 -0
  28. package/nitrogen/generated/android/c++/JFirmwareArtifactLease.hpp +57 -0
  29. package/nitrogen/generated/android/c++/JFirmwareArtifactLeaseCreateParams.hpp +57 -0
  30. package/nitrogen/generated/android/c++/JFirmwareArtifactLeaseReleaseParams.hpp +61 -0
  31. package/nitrogen/generated/android/c++/JFirmwareArtifactLeaseRetainParams.hpp +61 -0
  32. package/nitrogen/generated/android/c++/JFirmwareArtifactReaderCloseParams.hpp +57 -0
  33. package/nitrogen/generated/android/c++/JFirmwareArtifactReaderInfo.hpp +61 -0
  34. package/nitrogen/generated/android/c++/JFirmwareArtifactReaderReadParams.hpp +65 -0
  35. package/nitrogen/generated/android/c++/JFirmwareArtifactReceipt.hpp +69 -0
  36. package/nitrogen/generated/android/c++/JFirmwareArtifactRefParams.hpp +57 -0
  37. package/nitrogen/generated/android/c++/JFirmwareArtifactSweepResult.hpp +61 -0
  38. package/nitrogen/generated/android/c++/JFunc_void_RangeDownloadEvent.hpp +3 -3
  39. package/nitrogen/generated/android/c++/JHybridReactNativeRangeDownloaderSpec.cpp +274 -25
  40. package/nitrogen/generated/android/c++/JHybridReactNativeRangeDownloaderSpec.hpp +32 -22
  41. package/nitrogen/generated/android/c++/JRangeDownloadEvent.hpp +2 -2
  42. package/nitrogen/generated/android/c++/JRangeDownloadOutcome.hpp +6 -7
  43. package/nitrogen/generated/android/c++/JRangeDownloadParams.hpp +2 -2
  44. package/nitrogen/generated/android/c++/JRangeDownloadResult.hpp +2 -2
  45. package/nitrogen/generated/android/c++/JRangeFallbackKind.hpp +12 -13
  46. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/DownloadChannel.kt +5 -2
  47. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveExpectedEntry.kt +47 -0
  48. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveMaterializeParams.kt +44 -0
  49. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveMaterializeResult.kt +38 -0
  50. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArchiveMaterializedArtifact.kt +41 -0
  51. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactCancelParams.kt +38 -0
  52. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactCapabilities.kt +47 -0
  53. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactDownloadParams.kt +68 -0
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactLease.kt +38 -0
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactLeaseCreateParams.kt +38 -0
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactLeaseReleaseParams.kt +41 -0
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactLeaseRetainParams.kt +41 -0
  58. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactReaderCloseParams.kt +38 -0
  59. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactReaderInfo.kt +41 -0
  60. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactReaderReadParams.kt +44 -0
  61. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactReceipt.kt +47 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactRefParams.kt +38 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/FirmwareArtifactSweepResult.kt +41 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/Func_void_RangeDownloadEvent.kt +1 -1
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/HybridReactNativeRangeDownloaderSpec.kt +65 -19
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/RangeDownloadEvent.kt +2 -2
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/RangeDownloadOutcome.kt +3 -1
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/RangeDownloadParams.kt +2 -2
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/RangeDownloadResult.kt +2 -2
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/RangeFallbackKind.kt +3 -1
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativerangedownloader/reactnativerangedownloaderOnLoad.kt +1 -1
  72. package/nitrogen/generated/android/reactnativerangedownloader+autolinking.cmake +1 -1
  73. package/nitrogen/generated/android/reactnativerangedownloader+autolinking.gradle +1 -1
  74. package/nitrogen/generated/android/reactnativerangedownloaderOnLoad.cpp +28 -18
  75. package/nitrogen/generated/android/reactnativerangedownloaderOnLoad.hpp +14 -5
  76. package/nitrogen/generated/ios/ReactNativeRangeDownloader+autolinking.rb +2 -2
  77. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Bridge.cpp +49 -1
  78. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Bridge.hpp +348 -4
  79. package/nitrogen/generated/ios/ReactNativeRangeDownloader-Swift-Cxx-Umbrella.hpp +54 -1
  80. package/nitrogen/generated/ios/ReactNativeRangeDownloaderAutolinking.mm +1 -1
  81. package/nitrogen/generated/ios/ReactNativeRangeDownloaderAutolinking.swift +9 -8
  82. package/nitrogen/generated/ios/c++/HybridReactNativeRangeDownloaderSpecSwift.cpp +1 -1
  83. package/nitrogen/generated/ios/c++/HybridReactNativeRangeDownloaderSpecSwift.hpp +159 -1
  84. package/nitrogen/generated/ios/swift/DownloadChannel.swift +5 -1
  85. package/nitrogen/generated/ios/swift/FirmwareArchiveExpectedEntry.swift +44 -0
  86. package/nitrogen/generated/ios/swift/FirmwareArchiveMaterializeParams.swift +58 -0
  87. package/nitrogen/generated/ios/swift/FirmwareArchiveMaterializeResult.swift +35 -0
  88. package/nitrogen/generated/ios/swift/FirmwareArchiveMaterializedArtifact.swift +34 -0
  89. package/nitrogen/generated/ios/swift/FirmwareArtifactCancelParams.swift +29 -0
  90. package/nitrogen/generated/ios/swift/FirmwareArtifactCapabilities.swift +50 -0
  91. package/nitrogen/generated/ios/swift/FirmwareArtifactDownloadParams.swift +131 -0
  92. package/nitrogen/generated/ios/swift/FirmwareArtifactLease.swift +29 -0
  93. package/nitrogen/generated/ios/swift/FirmwareArtifactLeaseCreateParams.swift +29 -0
  94. package/nitrogen/generated/ios/swift/FirmwareArtifactLeaseReleaseParams.swift +34 -0
  95. package/nitrogen/generated/ios/swift/FirmwareArtifactLeaseRetainParams.swift +34 -0
  96. package/nitrogen/generated/ios/swift/FirmwareArtifactReaderCloseParams.swift +29 -0
  97. package/nitrogen/generated/ios/swift/FirmwareArtifactReaderInfo.swift +34 -0
  98. package/nitrogen/generated/ios/swift/FirmwareArtifactReaderReadParams.swift +39 -0
  99. package/nitrogen/generated/ios/swift/FirmwareArtifactReceipt.swift +44 -0
  100. package/nitrogen/generated/ios/swift/FirmwareArtifactRefParams.swift +29 -0
  101. package/nitrogen/generated/ios/swift/FirmwareArtifactSweepResult.swift +34 -0
  102. package/nitrogen/generated/ios/swift/Func_void.swift +1 -2
  103. package/nitrogen/generated/ios/swift/Func_void_FirmwareArchiveMaterializeResult.swift +46 -0
  104. package/nitrogen/generated/ios/swift/Func_void_FirmwareArtifactLease.swift +46 -0
  105. package/nitrogen/generated/ios/swift/Func_void_FirmwareArtifactReaderInfo.swift +46 -0
  106. package/nitrogen/generated/ios/swift/Func_void_FirmwareArtifactReceipt.swift +46 -0
  107. package/nitrogen/generated/ios/swift/Func_void_FirmwareArtifactSweepResult.swift +46 -0
  108. package/nitrogen/generated/ios/swift/Func_void_RangeDownloadEvent.swift +1 -2
  109. package/nitrogen/generated/ios/swift/Func_void_RangeDownloadResult.swift +1 -2
  110. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +1 -2
  111. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_ArrayBuffer_.swift +46 -0
  112. package/nitrogen/generated/ios/swift/HybridReactNativeRangeDownloaderSpec.swift +15 -4
  113. package/nitrogen/generated/ios/swift/HybridReactNativeRangeDownloaderSpec_cxx.swift +230 -2
  114. package/nitrogen/generated/ios/swift/RangeDownloadEvent.swift +11 -42
  115. package/nitrogen/generated/ios/swift/RangeDownloadOutcome.swift +1 -1
  116. package/nitrogen/generated/ios/swift/RangeDownloadParams.swift +72 -139
  117. package/nitrogen/generated/ios/swift/RangeDownloadResult.swift +16 -53
  118. package/nitrogen/generated/ios/swift/RangeFallbackKind.swift +1 -1
  119. package/nitrogen/generated/shared/c++/DownloadChannel.hpp +5 -1
  120. package/nitrogen/generated/shared/c++/FirmwareArchiveExpectedEntry.hpp +95 -0
  121. package/nitrogen/generated/shared/c++/FirmwareArchiveMaterializeParams.hpp +95 -0
  122. package/nitrogen/generated/shared/c++/FirmwareArchiveMaterializeResult.hpp +85 -0
  123. package/nitrogen/generated/shared/c++/FirmwareArchiveMaterializedArtifact.hpp +89 -0
  124. package/nitrogen/generated/shared/c++/FirmwareArtifactCancelParams.hpp +83 -0
  125. package/nitrogen/generated/shared/c++/FirmwareArtifactCapabilities.hpp +96 -0
  126. package/nitrogen/generated/shared/c++/FirmwareArtifactDownloadParams.hpp +124 -0
  127. package/nitrogen/generated/shared/c++/FirmwareArtifactLease.hpp +83 -0
  128. package/nitrogen/generated/shared/c++/FirmwareArtifactLeaseCreateParams.hpp +83 -0
  129. package/nitrogen/generated/shared/c++/FirmwareArtifactLeaseReleaseParams.hpp +87 -0
  130. package/nitrogen/generated/shared/c++/FirmwareArtifactLeaseRetainParams.hpp +87 -0
  131. package/nitrogen/generated/shared/c++/FirmwareArtifactReaderCloseParams.hpp +83 -0
  132. package/nitrogen/generated/shared/c++/FirmwareArtifactReaderInfo.hpp +87 -0
  133. package/nitrogen/generated/shared/c++/FirmwareArtifactReaderReadParams.hpp +91 -0
  134. package/nitrogen/generated/shared/c++/FirmwareArtifactReceipt.hpp +95 -0
  135. package/nitrogen/generated/shared/c++/FirmwareArtifactRefParams.hpp +83 -0
  136. package/nitrogen/generated/shared/c++/FirmwareArtifactSweepResult.hpp +87 -0
  137. package/nitrogen/generated/shared/c++/HybridReactNativeRangeDownloaderSpec.cpp +13 -1
  138. package/nitrogen/generated/shared/c++/HybridReactNativeRangeDownloaderSpec.hpp +59 -1
  139. package/nitrogen/generated/shared/c++/RangeDownloadEvent.hpp +25 -17
  140. package/nitrogen/generated/shared/c++/RangeDownloadOutcome.hpp +1 -1
  141. package/nitrogen/generated/shared/c++/RangeDownloadParams.hpp +43 -35
  142. package/nitrogen/generated/shared/c++/RangeDownloadResult.hpp +22 -14
  143. package/nitrogen/generated/shared/c++/RangeFallbackKind.hpp +1 -1
  144. package/package.json +4 -4
  145. package/src/ReactNativeRangeDownloader.nitro.ts +130 -1
  146. package/src/index.tsx +3 -1
@@ -15,6 +15,7 @@ Pod::Spec.new do |s|
15
15
 
16
16
  s.source_files = [
17
17
  "ios/**/*.{swift}",
18
+ "ios/**/*.{h}",
18
19
  "ios/**/*.{m,mm}",
19
20
  "cpp/**/*.{hpp,cpp}",
20
21
  ]
@@ -22,6 +23,12 @@ Pod::Spec.new do |s|
22
23
  s.dependency 'React-jsi'
23
24
  s.dependency 'React-callinvoker'
24
25
  s.dependency 'ReactNativeNativeLogger'
26
+ s.public_header_files = "ios/FirmwareArchiveMinizipBridge.h"
27
+ s.pod_target_xcconfig = {
28
+ 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/SSZipArchive/SSZipArchive/minizip"',
29
+ }
30
+
31
+ s.dependency 'SSZipArchive', '2.5.5'
25
32
 
26
33
  load 'nitrogen/generated/ios/ReactNativeRangeDownloader+autolinking.rb'
27
34
  add_nitrogen_files(s)
@@ -0,0 +1,291 @@
1
+ package com.margelo.nitro.reactnativerangedownloader
2
+
3
+ import java.io.File
4
+ import java.io.RandomAccessFile
5
+ import java.nio.ByteBuffer
6
+ import java.nio.ByteOrder
7
+ import java.nio.charset.CodingErrorAction
8
+ import java.nio.charset.StandardCharsets
9
+ import java.text.Normalizer
10
+ import java.util.Locale
11
+
12
+ internal data class FirmwareArchiveCentralEntry(
13
+ val name: String,
14
+ val compressedSize: Long,
15
+ val uncompressedSize: Long,
16
+ val crc32: Long,
17
+ val flags: Int,
18
+ val compressionMethod: Int,
19
+ val versionMadeBy: Int,
20
+ val externalAttributes: Long,
21
+ val diskNumber: Int,
22
+ )
23
+
24
+ internal object FirmwareArchiveRules {
25
+ private const val MAX_EOCD_SEARCH_BYTES = 65_557
26
+ private const val EOCD_SIGNATURE = 0x06054b50L
27
+ private const val CENTRAL_ENTRY_SIGNATURE = 0x02014b50L
28
+ private const val MAX_ARCHIVE_ENTRIES = 4096
29
+ private const val MAX_ARCHIVE_ENTRY_BYTES = 128L * 1024 * 1024
30
+ private const val MAX_ARCHIVE_EXPANDED_BYTES = 512L * 1024 * 1024
31
+ private val nestedArchiveExtensions = setOf(
32
+ ".zip",
33
+ ".7z",
34
+ ".rar",
35
+ ".tar",
36
+ ".gz",
37
+ ".tgz",
38
+ )
39
+ private val sha256Pattern = Regex("^[a-fA-F0-9]{64}$")
40
+
41
+ fun validateRequirements(
42
+ expectedEntries: Array<FirmwareArchiveExpectedEntry>,
43
+ ): List<FirmwareArchiveExpectedEntry> {
44
+ require(expectedEntries.isNotEmpty() && expectedEntries.size <= MAX_ARCHIVE_ENTRIES) {
45
+ "Firmware archive expected entry count is invalid"
46
+ }
47
+ val names = mutableSetOf<String>()
48
+ val canonicalNames = mutableSetOf<String>()
49
+ var totalSize = 0L
50
+ expectedEntries.forEach { entry ->
51
+ require(
52
+ entry.artifactId.isNotEmpty() &&
53
+ entry.artifactId.length <= 160 &&
54
+ entry.artifactId.all { it.isLetterOrDigit() || it in "._-" }
55
+ ) {
56
+ "Firmware archive artifactId is invalid"
57
+ }
58
+ require(
59
+ names.add(entry.entryName) &&
60
+ validatePortableName(entry.entryName, canonicalNames)
61
+ ) {
62
+ "Firmware archive expected entry name is invalid"
63
+ }
64
+ val expectedSize = entry.expectedSize.toExactPositiveLong("entry size")
65
+ require(expectedSize <= MAX_ARCHIVE_ENTRY_BYTES) {
66
+ "Firmware archive entry exceeds size limits"
67
+ }
68
+ totalSize = Math.addExact(totalSize, expectedSize)
69
+ require(totalSize <= MAX_ARCHIVE_EXPANDED_BYTES) {
70
+ "Firmware archive expected entries exceed size limits"
71
+ }
72
+ require(sha256Pattern.matches(entry.expectedSha256)) {
73
+ "Firmware archive entry SHA-256 is invalid"
74
+ }
75
+ }
76
+ return expectedEntries.toList()
77
+ }
78
+
79
+ fun validateCentralDirectory(
80
+ file: File,
81
+ requirements: List<FirmwareArchiveExpectedEntry>?,
82
+ ): List<FirmwareArchiveCentralEntry> {
83
+ val entries = scanCentralDirectory(file)
84
+ require(requirements == null || entries.size == requirements.size) {
85
+ "Firmware archive has missing or extra entries"
86
+ }
87
+ val requirementsByName = requirements?.associateBy { it.entryName }.orEmpty()
88
+ val names = mutableSetOf<String>()
89
+ val canonicalNames = mutableSetOf<String>()
90
+ var totalSize = 0L
91
+ entries.forEach { entry ->
92
+ val requirement = requirementsByName[entry.name]
93
+ require(requirements == null || requirement != null) {
94
+ "Firmware archive contains an unexpected entry"
95
+ }
96
+ val expectedSize = requirement?.expectedSize?.toLong()
97
+ ?: entry.uncompressedSize
98
+ totalSize = Math.addExact(totalSize, entry.uncompressedSize)
99
+ require(
100
+ names.add(entry.name) &&
101
+ validatePortableName(entry.name, canonicalNames) &&
102
+ entry.uncompressedSize == expectedSize &&
103
+ entry.uncompressedSize > 0 &&
104
+ entry.uncompressedSize <= MAX_ARCHIVE_ENTRY_BYTES &&
105
+ totalSize <= MAX_ARCHIVE_EXPANDED_BYTES &&
106
+ entry.compressedSize in 0..MAX_ARCHIVE_EXPANDED_BYTES &&
107
+ entry.uncompressedSize <=
108
+ Math.multiplyExact(entry.compressedSize.coerceAtLeast(1), 1000) &&
109
+ entry.diskNumber == 0 &&
110
+ entry.flags and 1 == 0 &&
111
+ (entry.compressionMethod == 0 || entry.compressionMethod == 8) &&
112
+ isRegularEntry(entry)
113
+ ) {
114
+ "Firmware archive entry metadata is invalid"
115
+ }
116
+ }
117
+ return entries
118
+ }
119
+
120
+ private fun scanCentralDirectory(file: File): List<FirmwareArchiveCentralEntry> {
121
+ require(file.isFile && file.length() >= 22) {
122
+ "Firmware archive is invalid"
123
+ }
124
+ RandomAccessFile(file, "r").use { archive ->
125
+ val tailSize = minOf(file.length(), MAX_EOCD_SEARCH_BYTES.toLong()).toInt()
126
+ val tail = ByteArray(tailSize)
127
+ archive.seek(file.length() - tailSize)
128
+ archive.readFully(tail)
129
+ val eocdIndex = findSignatureBackwards(tail, EOCD_SIGNATURE)
130
+ require(eocdIndex >= 0 && eocdIndex + 22 <= tail.size) {
131
+ "Firmware archive EOCD is missing"
132
+ }
133
+ val eocd = ByteBuffer.wrap(tail, eocdIndex, tail.size - eocdIndex)
134
+ .order(ByteOrder.LITTLE_ENDIAN)
135
+ require(eocd.int.toUnsignedLong() == EOCD_SIGNATURE) {
136
+ "Firmware archive EOCD is invalid"
137
+ }
138
+ val diskNumber = eocd.short.toUnsignedInt()
139
+ val centralDisk = eocd.short.toUnsignedInt()
140
+ val entriesOnDisk = eocd.short.toUnsignedInt()
141
+ val totalEntries = eocd.short.toUnsignedInt()
142
+ val centralSize = eocd.int.toUnsignedLong()
143
+ val centralOffset = eocd.int.toUnsignedLong()
144
+ val commentLength = eocd.short.toUnsignedInt()
145
+ require(
146
+ diskNumber == 0 &&
147
+ centralDisk == 0 &&
148
+ entriesOnDisk == totalEntries &&
149
+ totalEntries in 1..MAX_ARCHIVE_ENTRIES &&
150
+ totalEntries != 0xffff &&
151
+ centralSize != 0xffff_ffffL &&
152
+ centralOffset != 0xffff_ffffL &&
153
+ eocdIndex + 22 + commentLength == tail.size &&
154
+ centralOffset <= file.length() - centralSize &&
155
+ centralOffset + centralSize <= file.length() - tailSize + eocdIndex
156
+ ) {
157
+ "Firmware archive is multi-disk, ZIP64, or malformed"
158
+ }
159
+
160
+ archive.seek(centralOffset)
161
+ val entries = ArrayList<FirmwareArchiveCentralEntry>(totalEntries)
162
+ repeat(totalEntries) {
163
+ val header = ByteArray(46)
164
+ archive.readFully(header)
165
+ val buffer = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN)
166
+ require(buffer.int.toUnsignedLong() == CENTRAL_ENTRY_SIGNATURE) {
167
+ "Firmware archive central entry is malformed"
168
+ }
169
+ val versionMadeBy = buffer.short.toUnsignedInt()
170
+ buffer.short
171
+ val flags = buffer.short.toUnsignedInt()
172
+ val method = buffer.short.toUnsignedInt()
173
+ buffer.position(buffer.position() + 4)
174
+ val crc32 = buffer.int.toUnsignedLong()
175
+ val compressedSize = buffer.int.toUnsignedLong()
176
+ val uncompressedSize = buffer.int.toUnsignedLong()
177
+ val nameLength = buffer.short.toUnsignedInt()
178
+ val extraLength = buffer.short.toUnsignedInt()
179
+ val entryCommentLength = buffer.short.toUnsignedInt()
180
+ val entryDisk = buffer.short.toUnsignedInt()
181
+ buffer.short
182
+ val externalAttributes = buffer.int.toUnsignedLong()
183
+ val localHeaderOffset = buffer.int.toUnsignedLong()
184
+ require(
185
+ nameLength in 1..4096 &&
186
+ compressedSize != 0xffff_ffffL &&
187
+ uncompressedSize != 0xffff_ffffL &&
188
+ entryDisk != 0xffff &&
189
+ localHeaderOffset != 0xffff_ffffL &&
190
+ localHeaderOffset < centralOffset
191
+ ) {
192
+ "Firmware archive ZIP64 entry is not supported"
193
+ }
194
+ val nameBytes = ByteArray(nameLength)
195
+ archive.readFully(nameBytes)
196
+ val name = decodeEntryName(nameBytes, flags)
197
+ val skipLength = Math.addExact(extraLength, entryCommentLength)
198
+ archive.seek(Math.addExact(archive.filePointer, skipLength.toLong()))
199
+ entries += FirmwareArchiveCentralEntry(
200
+ name = name,
201
+ compressedSize = compressedSize,
202
+ uncompressedSize = uncompressedSize,
203
+ crc32 = crc32,
204
+ flags = flags,
205
+ compressionMethod = method,
206
+ versionMadeBy = versionMadeBy,
207
+ externalAttributes = externalAttributes,
208
+ diskNumber = entryDisk,
209
+ )
210
+ }
211
+ require(archive.filePointer == centralOffset + centralSize) {
212
+ "Firmware archive central directory size mismatch"
213
+ }
214
+ return entries
215
+ }
216
+ }
217
+
218
+ private fun decodeEntryName(bytes: ByteArray, flags: Int): String {
219
+ if (flags and (1 shl 11) == 0) {
220
+ require(bytes.all { it.toInt() and 0xff < 0x80 }) {
221
+ "Firmware archive entry name must be UTF-8 or ASCII"
222
+ }
223
+ return String(bytes, StandardCharsets.US_ASCII)
224
+ }
225
+ val decoder = StandardCharsets.UTF_8.newDecoder()
226
+ .onMalformedInput(CodingErrorAction.REPORT)
227
+ .onUnmappableCharacter(CodingErrorAction.REPORT)
228
+ return decoder.decode(ByteBuffer.wrap(bytes)).toString()
229
+ }
230
+
231
+ private fun validatePortableName(
232
+ name: String,
233
+ canonicalNames: MutableSet<String>,
234
+ ): Boolean {
235
+ val normalized = Normalizer.normalize(name, Normalizer.Form.NFC)
236
+ val folded = normalized.lowercase(Locale.ROOT)
237
+ val components = name.split('/')
238
+ return name.isNotEmpty() &&
239
+ name.length <= 512 &&
240
+ name == normalized &&
241
+ !name.startsWith("/") &&
242
+ !name.startsWith("\\") &&
243
+ '\\' !in name &&
244
+ ':' !in name &&
245
+ name.none { it.code < 0x20 || it.code == 0x7f } &&
246
+ components.all {
247
+ it.isNotEmpty() &&
248
+ it != "." &&
249
+ it != ".." &&
250
+ !it.endsWith(".") &&
251
+ !it.endsWith(" ")
252
+ } &&
253
+ nestedArchiveExtensions.none { folded.endsWith(it) } &&
254
+ canonicalNames.add(folded)
255
+ }
256
+
257
+ private fun isRegularEntry(entry: FirmwareArchiveCentralEntry): Boolean {
258
+ val hostSystem = entry.versionMadeBy ushr 8
259
+ if (hostSystem == 3 || hostSystem == 19) {
260
+ val fileType = (entry.externalAttributes ushr 16) and 0xf000
261
+ return fileType == 0L || fileType == 0x8000L
262
+ }
263
+ return entry.externalAttributes and 0x10 == 0L
264
+ }
265
+
266
+ private fun findSignatureBackwards(bytes: ByteArray, signature: Long): Int {
267
+ for (index in bytes.size - 4 downTo 0) {
268
+ val value = (bytes[index].toLong() and 0xff) or
269
+ ((bytes[index + 1].toLong() and 0xff) shl 8) or
270
+ ((bytes[index + 2].toLong() and 0xff) shl 16) or
271
+ ((bytes[index + 3].toLong() and 0xff) shl 24)
272
+ if (value == signature) return index
273
+ }
274
+ return -1
275
+ }
276
+
277
+ private fun Int.toUnsignedLong(): Long = toLong() and 0xffff_ffffL
278
+
279
+ private fun Short.toUnsignedInt(): Int = toInt() and 0xffff
280
+
281
+ private fun Double.toExactPositiveLong(label: String): Long {
282
+ require(isFinite() && this > 0 && this <= Long.MAX_VALUE.toDouble()) {
283
+ "Invalid firmware archive $label"
284
+ }
285
+ val converted = toLong()
286
+ require(converted.toDouble() == this) {
287
+ "Invalid firmware archive $label"
288
+ }
289
+ return converted
290
+ }
291
+ }
@@ -0,0 +1,111 @@
1
+ package com.margelo.nitro.reactnativerangedownloader
2
+
3
+ import java.io.File
4
+
5
+ internal const val FIRMWARE_ARTIFACT_FINAL_GRACE_MS = 24L * 60 * 60 * 1000
6
+ internal const val FIRMWARE_ARTIFACT_PARTIAL_GRACE_MS = 7L * 24 * 60 * 60 * 1000
7
+ internal const val FIRMWARE_ARTIFACT_SCRATCH_GRACE_MS =
8
+ FIRMWARE_ARTIFACT_PARTIAL_GRACE_MS
9
+
10
+ private val firmwareArtifactSha256Pattern = Regex("^[a-f0-9]{64}$")
11
+ private val firmwareArchiveScratchPattern = Regex(
12
+ "^archive-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" +
13
+ "[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
14
+ )
15
+ private val firmwarePromoteScratchPattern = Regex(
16
+ "^\\.promote-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" +
17
+ "[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
18
+ )
19
+
20
+ private fun File.isSymbolicLinkEntry(): Boolean = try {
21
+ val canonicalParent = parentFile?.canonicalFile
22
+ val entryFromCanonicalParent = canonicalParent?.let { File(it, name) } ?: this
23
+ entryFromCanonicalParent.canonicalFile != entryFromCanonicalParent.absoluteFile
24
+ } catch (_: Exception) {
25
+ true
26
+ }
27
+
28
+ private fun firmwareArtifactEntrySize(entry: File): Long {
29
+ if (entry.isSymbolicLinkEntry()) return 0
30
+ if (entry.isFile) return entry.length()
31
+ if (!entry.isDirectory) return 0
32
+ return entry.listFiles()?.sumOf(::firmwareArtifactEntrySize) ?: 0
33
+ }
34
+
35
+ private fun deleteFirmwareArtifactEntry(entry: File): Boolean {
36
+ if (entry.isSymbolicLinkEntry()) return entry.delete()
37
+ if (entry.isDirectory) {
38
+ val children = entry.listFiles() ?: return false
39
+ if (children.any { !deleteFirmwareArtifactEntry(it) }) return false
40
+ }
41
+ return entry.delete()
42
+ }
43
+
44
+ private fun firmwareArtifactEntryExceededGrace(
45
+ entry: File,
46
+ nowMs: Long,
47
+ graceMs: Long,
48
+ ): Boolean {
49
+ val modifiedAt = entry.lastModified()
50
+ return modifiedAt > 0 && modifiedAt <= nowMs && nowMs - modifiedAt >= graceMs
51
+ }
52
+
53
+ internal fun sweepFirmwareArtifactOrphansAtRoot(
54
+ root: File,
55
+ retainedSha256: Set<String>,
56
+ activeSha256: Set<String>,
57
+ openPaths: Set<String>,
58
+ nowMs: Long = System.currentTimeMillis(),
59
+ ): Pair<Int, Long> {
60
+ var deletedFiles = 0
61
+ var deletedBytes = 0L
62
+ root.listFiles()?.forEach { entry ->
63
+ val isSymbolicLink = entry.isSymbolicLinkEntry()
64
+ val isArchiveScratch = firmwareArchiveScratchPattern.matches(entry.name)
65
+ val isPromoteScratch = firmwarePromoteScratchPattern.matches(entry.name)
66
+ val isScratchCandidate = !isSymbolicLink &&
67
+ ((isArchiveScratch && entry.isDirectory) ||
68
+ (isPromoteScratch && entry.isFile))
69
+ if (isScratchCandidate) {
70
+ if (
71
+ firmwareArtifactEntryExceededGrace(
72
+ entry,
73
+ nowMs,
74
+ FIRMWARE_ARTIFACT_SCRATCH_GRACE_MS,
75
+ )
76
+ ) {
77
+ val size = firmwareArtifactEntrySize(entry)
78
+ if (deleteFirmwareArtifactEntry(entry)) {
79
+ deletedFiles += 1
80
+ deletedBytes += size
81
+ }
82
+ }
83
+ return@forEach
84
+ }
85
+
86
+ if (!entry.isFile || isSymbolicLink || entry.name.length < 64) return@forEach
87
+ val sha256 = entry.name.take(64)
88
+ if (
89
+ !firmwareArtifactSha256Pattern.matches(sha256) ||
90
+ sha256 in retainedSha256 ||
91
+ sha256 in activeSha256 ||
92
+ entry.absolutePath in openPaths
93
+ ) {
94
+ return@forEach
95
+ }
96
+ val grace = if (entry.name.endsWith(".bin")) {
97
+ FIRMWARE_ARTIFACT_FINAL_GRACE_MS
98
+ } else if (entry.name.endsWith(".partial")) {
99
+ FIRMWARE_ARTIFACT_PARTIAL_GRACE_MS
100
+ } else {
101
+ return@forEach
102
+ }
103
+ if (!firmwareArtifactEntryExceededGrace(entry, nowMs, grace)) return@forEach
104
+ val size = entry.length()
105
+ if (entry.delete()) {
106
+ deletedFiles += 1
107
+ deletedBytes += size
108
+ }
109
+ }
110
+ return deletedFiles to deletedBytes
111
+ }