@hot-updater/react-native 1.0.0-rc.1 → 1.0.0-rc.15
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/android/proguard-rules.pro +17 -2
- package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +651 -637
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +10 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +0 -13
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +10 -14
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +19 -0
- package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
- package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
- package/android/src/newarch/HotUpdater.kt +7 -9
- package/android/src/newarch/HotUpdaterModule.kt +40 -20
- package/android/src/oldarch/HotUpdater.kt +7 -9
- package/android/src/oldarch/HotUpdaterModule.kt +40 -20
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +1126 -74
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -5
- package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +97 -0
- package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +44 -0
- package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +206 -0
- package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +773 -1016
- package/ios/HotUpdater/Internal/BundleMetadata.swift +5 -0
- package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
- package/ios/HotUpdater/Internal/HotUpdater.mm +5 -27
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +149 -51
- package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
- package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +13 -72
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
- package/lib/commonjs/checkForUpdate.js +32 -23
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +97 -28
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/commonjs/error.js +4 -13
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/fetchJSON.js +9 -2
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/httpClient.js +46 -29
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/httpClient.spec.js +57 -5
- package/lib/commonjs/httpClient.spec.js.map +1 -1
- package/lib/commonjs/index.js +31 -14
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +56 -11
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +15 -14
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +128 -22
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +28 -21
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +1 -0
- package/lib/commonjs/notifyAppReadyInsights.js +188 -0
- package/lib/commonjs/notifyAppReadyInsights.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +161 -29
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +14 -7
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +1 -0
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/sdkVersion.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/store.js +3 -10
- package/lib/commonjs/store.js.map +1 -1
- package/lib/commonjs/store.notification.spec.js +12 -4
- package/lib/commonjs/store.notification.spec.js.map +1 -1
- package/lib/commonjs/store.spec.js +2 -136
- package/lib/commonjs/store.spec.js.map +1 -1
- package/lib/commonjs/wrap.js +11 -12
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +97 -25
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/checkForUpdate.js +32 -23
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +97 -28
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/module/error.js +4 -13
- package/lib/module/error.js.map +1 -1
- package/lib/module/fetchJSON.js +7 -1
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/httpClient.js +48 -31
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/httpClient.spec.js +57 -5
- package/lib/module/httpClient.spec.js.map +1 -1
- package/lib/module/index.js +32 -15
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +56 -11
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +13 -12
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +128 -22
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +23 -16
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +1 -0
- package/lib/module/notifyAppReadyInsights.js +181 -0
- package/lib/module/notifyAppReadyInsights.js.map +1 -0
- package/lib/module/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +153 -21
- package/lib/module/notifyAppReadyInsights.spec.js.map +1 -0
- package/lib/module/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +1 -0
- package/lib/module/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +11 -4
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +1 -0
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/sdkVersion.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/store.js +3 -10
- package/lib/module/store.js.map +1 -1
- package/lib/module/store.notification.spec.js +12 -4
- package/lib/module/store.notification.spec.js.map +1 -1
- package/lib/module/store.spec.js +2 -136
- package/lib/module/store.spec.js.map +1 -1
- package/lib/module/wrap.js +11 -12
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +99 -25
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -3
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/error.d.ts +4 -13
- package/lib/typescript/commonjs/error.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchJSON.d.ts +4 -0
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +6 -6
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -7
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +5 -9
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +25 -0
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +1 -0
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +8 -0
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +8 -35
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/store.d.ts +1 -3
- package/lib/typescript/commonjs/store.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +5 -8
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts +2 -3
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/error.d.ts +4 -13
- package/lib/typescript/module/error.d.ts.map +1 -1
- package/lib/typescript/module/fetchJSON.d.ts +4 -0
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +6 -6
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -7
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +5 -9
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/notifyAppReadyInsights.d.ts +25 -0
- package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +1 -0
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +8 -0
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +8 -35
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/store.d.ts +1 -3
- package/lib/typescript/module/store.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +5 -8
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/checkForUpdate.releaseCatalog.spec.ts +137 -61
- package/src/checkForUpdate.ts +35 -29
- package/src/error.ts +4 -14
- package/src/fetchJSON.ts +12 -1
- package/src/httpClient.spec.ts +70 -5
- package/src/httpClient.ts +78 -64
- package/src/index.spec.ts +59 -11
- package/src/index.ts +41 -17
- package/src/native.spec.ts +133 -22
- package/src/native.ts +21 -32
- package/src/{notifyAppReadyAnalytics.boundary.spec.ts → notifyAppReadyInsights.boundary.spec.ts} +25 -17
- package/src/{notifyAppReadyAnalytics.spec.ts → notifyAppReadyInsights.spec.ts} +141 -22
- package/src/{notifyAppReadyAnalytics.test-utils.ts → notifyAppReadyInsights.test-utils.ts} +4 -4
- package/src/{notifyAppReadyAnalytics.transport.spec.ts → notifyAppReadyInsights.transport.spec.ts} +13 -5
- package/src/notifyAppReadyInsights.ts +293 -0
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +8 -36
- package/src/store.notification.spec.ts +13 -3
- package/src/store.spec.ts +0 -157
- package/src/store.ts +2 -23
- package/src/wrap.spec.ts +109 -31
- package/src/wrap.tsx +11 -11
- package/src/wrap.types.ts +5 -8
- package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
- package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
- package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
- package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
- package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
- package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
- package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
- package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
- package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
- package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
- package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
- package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
- package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyAnalytics.js +0 -133
- package/lib/commonjs/notifyAppReadyAnalytics.js.map +0 -1
- package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.js +0 -128
- package/lib/module/notifyAppReadyAnalytics.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.spec.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts +0 -13
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyAnalytics.d.ts +0 -13
- package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
- package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
- package/src/notifyAppReadyAnalytics.ts +0 -194
|
@@ -3,480 +3,319 @@ package com.hotupdater
|
|
|
3
3
|
import java.io.EOFException
|
|
4
4
|
import java.io.IOException
|
|
5
5
|
import java.io.InputStream
|
|
6
|
+
import java.nio.ByteBuffer
|
|
7
|
+
import java.nio.charset.CodingErrorAction
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
internal data class TarArchiveEntry(
|
|
10
|
+
val name: String,
|
|
11
|
+
val size: Long,
|
|
12
|
+
val typeFlag: Char,
|
|
13
|
+
) {
|
|
14
|
+
val isRegularFile: Boolean
|
|
15
|
+
get() = typeFlag == '0' || typeFlag == '\u0000'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Small, strict TAR reader for the deterministic bundle.tar.br transport. */
|
|
19
|
+
internal class TarArchiveInputStream(
|
|
12
20
|
private val input: InputStream,
|
|
13
21
|
) : InputStream() {
|
|
14
|
-
private var currentEntry: TarArchiveEntry? = null
|
|
15
|
-
private var currentEntryBytesRead: Long = 0
|
|
16
|
-
private var pendingName: String? = null
|
|
17
|
-
|
|
18
22
|
companion object {
|
|
19
|
-
private const val TAG = "TarInputStream"
|
|
20
23
|
private const val BLOCK_SIZE = 512
|
|
21
|
-
private const val
|
|
22
|
-
private const val NAME_LENGTH = 100
|
|
23
|
-
private const val MODE_OFFSET = 100
|
|
24
|
-
private const val SIZE_OFFSET = 124
|
|
25
|
-
private const val SIZE_LENGTH = 12
|
|
26
|
-
private const val CHECKSUM_OFFSET = 148
|
|
27
|
-
private const val CHECKSUM_LENGTH = 8
|
|
28
|
-
private const val TYPEFLAG_OFFSET = 156
|
|
29
|
-
private const val LINKNAME_OFFSET = 157
|
|
30
|
-
private const val LINKNAME_LENGTH = 100
|
|
31
|
-
private const val MAGIC_OFFSET = 257
|
|
32
|
-
private const val PREFIX_OFFSET = 345
|
|
33
|
-
private const val PREFIX_LENGTH = 155
|
|
34
|
-
|
|
35
|
-
// Maximum file size: 1GB per file
|
|
36
|
-
private const val MAX_FILE_SIZE = 1_073_741_824L
|
|
24
|
+
private const val MAX_EXTENSION_SIZE = 1024 * 1024L
|
|
37
25
|
}
|
|
38
26
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Skip remaining bytes of current entry
|
|
44
|
-
if (currentEntry != null) {
|
|
45
|
-
val remaining = currentEntry!!.size - currentEntryBytesRead
|
|
46
|
-
if (remaining > 0) {
|
|
47
|
-
skipBytes(remaining)
|
|
48
|
-
}
|
|
49
|
-
skipPadding(currentEntry!!.size)
|
|
50
|
-
}
|
|
27
|
+
private var currentEntry: TarArchiveEntry? = null
|
|
28
|
+
private var currentEntryBytesRead = 0L
|
|
29
|
+
private var pendingPaxHeaders: Map<String, String>? = null
|
|
30
|
+
private var finished = false
|
|
51
31
|
|
|
52
|
-
|
|
32
|
+
fun getNextEntry(): TarArchiveEntry? {
|
|
33
|
+
if (finished) return null
|
|
34
|
+
finishCurrentEntry()
|
|
53
35
|
|
|
54
36
|
while (true) {
|
|
55
|
-
val
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
37
|
+
val header = readBlock(required = true)
|
|
38
|
+
if (header.all { it == 0.toByte() }) {
|
|
39
|
+
val secondEndBlock = readBlock(required = true)
|
|
40
|
+
if (!secondEndBlock.all { it == 0.toByte() } || pendingPaxHeaders != null) {
|
|
41
|
+
throw IOException("Invalid TAR termination")
|
|
42
|
+
}
|
|
43
|
+
consumeTrailingZeroBlocks()
|
|
44
|
+
finished = true
|
|
59
45
|
return null
|
|
60
46
|
}
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Parse header
|
|
72
|
-
val entry = parseHeader(headerBytes)
|
|
73
|
-
|
|
74
|
-
// Handle GNU long filename extension
|
|
75
|
-
if (entry.typeFlag == 'L') {
|
|
76
|
-
pendingName = readLongName(entry.size)
|
|
77
|
-
continue
|
|
78
|
-
}
|
|
48
|
+
verifyHeader(header)
|
|
49
|
+
val entry = parseHeader(header)
|
|
50
|
+
when (entry.typeFlag) {
|
|
51
|
+
'x' -> {
|
|
52
|
+
if (pendingPaxHeaders != null) throw IOException("Consecutive PAX headers are not supported")
|
|
53
|
+
pendingPaxHeaders = readPaxHeaders(entry.size)
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
79
56
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
continue
|
|
84
|
-
}
|
|
57
|
+
'g' -> {
|
|
58
|
+
throw IOException("Global PAX headers are not supported")
|
|
59
|
+
}
|
|
85
60
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
pendingName = null
|
|
61
|
+
'L' -> {
|
|
62
|
+
throw IOException("GNU long names are not supported")
|
|
63
|
+
}
|
|
90
64
|
}
|
|
91
65
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
66
|
+
val paxHeaders = pendingPaxHeaders.orEmpty()
|
|
67
|
+
pendingPaxHeaders = null
|
|
68
|
+
if (paxHeaders.containsKey("linkpath")) throw IOException("TAR links are not allowed")
|
|
69
|
+
val resolvedEntry =
|
|
70
|
+
entry.copy(
|
|
71
|
+
name = paxHeaders["path"] ?: entry.name,
|
|
72
|
+
size = paxHeaders["size"]?.let(::parsePaxSize) ?: entry.size,
|
|
73
|
+
)
|
|
74
|
+
currentEntry = resolvedEntry
|
|
75
|
+
currentEntryBytesRead = 0
|
|
76
|
+
return resolvedEntry
|
|
97
77
|
}
|
|
98
78
|
}
|
|
99
79
|
|
|
100
80
|
override fun read(): Int {
|
|
101
|
-
val
|
|
102
|
-
|
|
103
|
-
return if (n <= 0) -1 else b[0].toInt() and 0xFF
|
|
81
|
+
val byte = ByteArray(1)
|
|
82
|
+
return if (read(byte, 0, 1) == -1) -1 else byte[0].toInt() and 0xff
|
|
104
83
|
}
|
|
105
84
|
|
|
106
85
|
override fun read(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
86
|
+
buffer: ByteArray,
|
|
87
|
+
offset: Int,
|
|
88
|
+
length: Int,
|
|
110
89
|
): Int {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
val toRead = minOf(len.toLong(), remaining).toInt()
|
|
121
|
-
val bytesRead = input.read(b, off, toRead)
|
|
122
|
-
|
|
123
|
-
if (bytesRead > 0) {
|
|
124
|
-
currentEntryBytesRead += bytesRead
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return bytesRead
|
|
90
|
+
val entry = currentEntry ?: throw IllegalStateException("No current TAR entry")
|
|
91
|
+
val remaining = entry.size - currentEntryBytesRead
|
|
92
|
+
if (remaining == 0L) return -1
|
|
93
|
+
val count = input.read(buffer, offset, minOf(length.toLong(), remaining).toInt())
|
|
94
|
+
if (count < 0) throw EOFException("TAR entry ${entry.name} is truncated")
|
|
95
|
+
if (count == 0) throw IOException("TAR entry read made no progress")
|
|
96
|
+
currentEntryBytesRead += count
|
|
97
|
+
return count
|
|
128
98
|
}
|
|
129
99
|
|
|
130
|
-
|
|
131
|
-
|
|
100
|
+
private fun finishCurrentEntry() {
|
|
101
|
+
val entry = currentEntry ?: return
|
|
102
|
+
skipExactly(entry.size - currentEntryBytesRead)
|
|
103
|
+
readPadding(entry.size)
|
|
104
|
+
currentEntry = null
|
|
105
|
+
currentEntryBytesRead = 0
|
|
132
106
|
}
|
|
133
107
|
|
|
134
|
-
|
|
135
|
-
* Read a 512-byte block from input
|
|
136
|
-
*/
|
|
137
|
-
private fun readBlock(): ByteArray? {
|
|
108
|
+
private fun readBlock(required: Boolean): ByteArray {
|
|
138
109
|
val block = ByteArray(BLOCK_SIZE)
|
|
139
110
|
var offset = 0
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
111
|
+
while (offset < block.size) {
|
|
112
|
+
val count = input.read(block, offset, block.size - offset)
|
|
113
|
+
if (count < 0) {
|
|
114
|
+
if (!required && offset == 0) return ByteArray(0)
|
|
115
|
+
throw EOFException("Unexpected end of TAR archive")
|
|
145
116
|
}
|
|
146
|
-
|
|
117
|
+
if (count == 0) throw IOException("TAR stream made no progress")
|
|
118
|
+
offset += count
|
|
147
119
|
}
|
|
148
|
-
|
|
149
120
|
return block
|
|
150
121
|
}
|
|
151
122
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
private fun isValidHeader(header: ByteArray): Boolean {
|
|
161
|
-
// Check for "ustar" magic (may have \0 or space after)
|
|
162
|
-
val magic = String(header, MAGIC_OFFSET, 5, Charsets.US_ASCII)
|
|
163
|
-
return magic == "ustar"
|
|
123
|
+
private fun consumeTrailingZeroBlocks() {
|
|
124
|
+
while (true) {
|
|
125
|
+
val block = readBlock(required = false)
|
|
126
|
+
if (block.isEmpty()) return
|
|
127
|
+
if (!block.all { it == 0.toByte() }) {
|
|
128
|
+
throw IOException("TAR archive has trailing non-zero data")
|
|
129
|
+
}
|
|
130
|
+
}
|
|
164
131
|
}
|
|
165
132
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
val storedChecksum = parseOctal(header,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
} else {
|
|
181
|
-
header[i].toInt()
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
unsignedSum += value and 0xFF
|
|
185
|
-
signedSum += value.toByte().toInt()
|
|
133
|
+
private fun verifyHeader(header: ByteArray) {
|
|
134
|
+
val magic = String(header, 257, 5, Charsets.US_ASCII)
|
|
135
|
+
if (magic != "ustar") throw IOException("Invalid TAR magic")
|
|
136
|
+
|
|
137
|
+
val storedChecksum = parseOctal(header, 148, 8)
|
|
138
|
+
var unsignedChecksum = 0L
|
|
139
|
+
var signedChecksum = 0L
|
|
140
|
+
header.forEachIndexed { index, byte ->
|
|
141
|
+
val value = if (index in 148 until 156) 32 else byte.toInt()
|
|
142
|
+
unsignedChecksum += value and 0xff
|
|
143
|
+
signedChecksum += value.toByte().toInt()
|
|
144
|
+
}
|
|
145
|
+
if (storedChecksum != unsignedChecksum && storedChecksum != signedChecksum) {
|
|
146
|
+
throw IOException("TAR header checksum mismatch")
|
|
186
147
|
}
|
|
187
|
-
|
|
188
|
-
return storedChecksum == unsignedSum || storedChecksum == signedSum
|
|
189
148
|
}
|
|
190
149
|
|
|
191
|
-
/**
|
|
192
|
-
* Parse TAR header into TarArchiveEntry
|
|
193
|
-
*/
|
|
194
150
|
private fun parseHeader(header: ByteArray): TarArchiveEntry {
|
|
195
|
-
val name = parseString(header,
|
|
196
|
-
val
|
|
197
|
-
val
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
val fullName = if (prefix.isNotEmpty()) "$prefix/$name" else name
|
|
204
|
-
|
|
151
|
+
val name = parseString(header, 0, 100)
|
|
152
|
+
val prefix = parseString(header, 345, 155)
|
|
153
|
+
val fullName =
|
|
154
|
+
when {
|
|
155
|
+
prefix.isEmpty() -> name
|
|
156
|
+
name.isEmpty() -> prefix
|
|
157
|
+
else -> "$prefix/$name"
|
|
158
|
+
}
|
|
205
159
|
return TarArchiveEntry(
|
|
206
160
|
name = fullName,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
typeFlag = typeFlag,
|
|
210
|
-
linkName = linkName,
|
|
161
|
+
size = parseNumeric(header, 124, 12),
|
|
162
|
+
typeFlag = header[156].toInt().toChar(),
|
|
211
163
|
)
|
|
212
164
|
}
|
|
213
165
|
|
|
214
|
-
/**
|
|
215
|
-
* Parse string field from header
|
|
216
|
-
*/
|
|
217
166
|
private fun parseString(
|
|
218
167
|
bytes: ByteArray,
|
|
219
168
|
offset: Int,
|
|
220
169
|
length: Int,
|
|
221
170
|
): String {
|
|
222
171
|
var end = offset
|
|
223
|
-
while (end < offset + length && bytes[end] != 0.toByte())
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
return String(bytes, offset, end - offset, Charsets.UTF_8).trim()
|
|
172
|
+
while (end < offset + length && bytes[end] != 0.toByte()) end++
|
|
173
|
+
return decodeUtf8(bytes, offset, end - offset)
|
|
227
174
|
}
|
|
228
175
|
|
|
229
|
-
|
|
230
|
-
* Parse octal number from header field
|
|
231
|
-
*/
|
|
232
|
-
private fun parseOctal(
|
|
176
|
+
private fun parseNumeric(
|
|
233
177
|
bytes: ByteArray,
|
|
234
178
|
offset: Int,
|
|
235
179
|
length: Int,
|
|
236
180
|
): Long {
|
|
181
|
+
if ((bytes[offset].toInt() and 0x80) == 0) {
|
|
182
|
+
return parseOctal(bytes, offset, length)
|
|
183
|
+
}
|
|
184
|
+
if ((bytes[offset].toInt() and 0x40) != 0) {
|
|
185
|
+
throw IOException("Negative TAR sizes are not supported")
|
|
186
|
+
}
|
|
237
187
|
var result = 0L
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// Parse octal digits
|
|
245
|
-
while (i < end) {
|
|
246
|
-
val b = bytes[i]
|
|
247
|
-
if (b == 0.toByte() || b == ' '.code.toByte()) break
|
|
248
|
-
if (b < '0'.code.toByte() || b > '7'.code.toByte()) {
|
|
249
|
-
throw IOException("Invalid octal digit: ${b.toInt()}")
|
|
188
|
+
for (index in offset until offset + length) {
|
|
189
|
+
val value = bytes[index].toInt() and 0xff
|
|
190
|
+
val payload = if (index == offset) value and 0x7f else value
|
|
191
|
+
if (result > (Long.MAX_VALUE - payload) / 256) {
|
|
192
|
+
throw IOException("TAR size overflows Long")
|
|
250
193
|
}
|
|
251
|
-
result = result *
|
|
252
|
-
i++
|
|
194
|
+
result = result * 256 + payload
|
|
253
195
|
}
|
|
254
|
-
|
|
255
196
|
return result
|
|
256
197
|
}
|
|
257
198
|
|
|
258
|
-
|
|
259
|
-
* Parse numeric field (supports both octal and base-256 encoding)
|
|
260
|
-
*/
|
|
261
|
-
private fun parseNumeric(
|
|
262
|
-
bytes: ByteArray,
|
|
263
|
-
offset: Int,
|
|
264
|
-
length: Int,
|
|
265
|
-
): Long {
|
|
266
|
-
// Check for base-256 encoding (high bit set)
|
|
267
|
-
if ((bytes[offset].toInt() and 0x80) != 0) {
|
|
268
|
-
return parseBase256(bytes, offset, length)
|
|
269
|
-
}
|
|
270
|
-
return parseOctal(bytes, offset, length)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Parse base-256 encoded number (for files > 8GB)
|
|
275
|
-
*/
|
|
276
|
-
private fun parseBase256(
|
|
199
|
+
private fun parseOctal(
|
|
277
200
|
bytes: ByteArray,
|
|
278
201
|
offset: Int,
|
|
279
202
|
length: Int,
|
|
280
203
|
): Long {
|
|
281
204
|
var result = 0L
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
205
|
+
var started = false
|
|
206
|
+
for (index in offset until offset + length) {
|
|
207
|
+
val value = bytes[index]
|
|
208
|
+
if (!started && (value == 0.toByte() || value == ' '.code.toByte())) continue
|
|
209
|
+
if (value == 0.toByte() || value == ' '.code.toByte()) break
|
|
210
|
+
if (value !in '0'.code.toByte()..'7'.code.toByte()) {
|
|
211
|
+
throw IOException("Invalid TAR octal value")
|
|
212
|
+
}
|
|
213
|
+
started = true
|
|
214
|
+
val digit = value - '0'.code.toByte()
|
|
215
|
+
if (result > (Long.MAX_VALUE - digit) / 8) {
|
|
216
|
+
throw IOException("TAR octal value overflows Long")
|
|
217
|
+
}
|
|
218
|
+
result = result * 8 + digit
|
|
286
219
|
}
|
|
287
|
-
|
|
288
220
|
return result
|
|
289
221
|
}
|
|
290
222
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
*/
|
|
294
|
-
private fun readLongName(size: Long): String {
|
|
295
|
-
val nameBytes = ByteArray(size.toInt())
|
|
296
|
-
var offset = 0
|
|
297
|
-
|
|
298
|
-
while (offset < size) {
|
|
299
|
-
val n = input.read(nameBytes, offset, size.toInt() - offset)
|
|
300
|
-
if (n < 0) throw EOFException("Unexpected end reading long name")
|
|
301
|
-
offset += n
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
skipPadding(size)
|
|
305
|
-
|
|
306
|
-
// Remove trailing NUL
|
|
307
|
-
val nameLength =
|
|
308
|
-
nameBytes
|
|
309
|
-
.indexOfFirst { it == 0.toByte() }
|
|
310
|
-
.takeIf { it >= 0 } ?: nameBytes.size
|
|
311
|
-
|
|
312
|
-
return String(nameBytes, 0, nameLength, Charsets.UTF_8)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Read the path attribute from a POSIX PAX extended header.
|
|
317
|
-
* Record lengths are measured in bytes and include the length itself.
|
|
318
|
-
*/
|
|
319
|
-
private fun readPaxPath(size: Long): String? {
|
|
320
|
-
if (size < 0 || size > MAX_FILE_SIZE) {
|
|
321
|
-
throw SecurityException("Invalid PAX header size: $size")
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
val data = ByteArray(size.toInt())
|
|
325
|
-
var bytesRead = 0
|
|
326
|
-
while (bytesRead < data.size) {
|
|
327
|
-
val count = input.read(data, bytesRead, data.size - bytesRead)
|
|
328
|
-
if (count < 0) throw EOFException("Unexpected end reading PAX header")
|
|
329
|
-
bytesRead += count
|
|
330
|
-
}
|
|
331
|
-
skipPadding(size)
|
|
332
|
-
|
|
223
|
+
private fun readPaxHeaders(size: Long): Map<String, String> {
|
|
224
|
+
val data = readExtension(size)
|
|
333
225
|
var offset = 0
|
|
334
|
-
|
|
226
|
+
val headers = linkedMapOf<String, String>()
|
|
335
227
|
while (offset < data.size) {
|
|
336
|
-
val
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (recordLength <= 0 || recordLength > data.size - offset) {
|
|
341
|
-
throw IOException("Invalid PAX record length: $recordLength")
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
val recordEnd = offset + recordLength
|
|
345
|
-
if (recordEnd <= space + 2 || data[recordEnd - 1] != '\n'.code.toByte()) {
|
|
346
|
-
throw IOException("Invalid PAX record")
|
|
228
|
+
val separator = data.indexOf(' '.code.toByte(), offset)
|
|
229
|
+
if (separator <= offset) throw IOException("Invalid PAX record length")
|
|
230
|
+
if ((offset until separator).any { data[it] !in '0'.code.toByte()..'9'.code.toByte() }) {
|
|
231
|
+
throw IOException("Invalid PAX record length")
|
|
347
232
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (
|
|
352
|
-
|
|
353
|
-
val key =
|
|
354
|
-
String(
|
|
355
|
-
data,
|
|
356
|
-
space + 1,
|
|
357
|
-
equals - space - 1,
|
|
358
|
-
Charsets.UTF_8,
|
|
359
|
-
)
|
|
360
|
-
if (key == "path") {
|
|
361
|
-
path =
|
|
362
|
-
String(
|
|
363
|
-
data,
|
|
364
|
-
equals + 1,
|
|
365
|
-
valueEnd - equals - 1,
|
|
366
|
-
Charsets.UTF_8,
|
|
367
|
-
)
|
|
233
|
+
val recordLength =
|
|
234
|
+
String(data, offset, separator - offset, Charsets.US_ASCII).toIntOrNull()
|
|
235
|
+
?: throw IOException("Invalid PAX record length")
|
|
236
|
+
if (recordLength <= separator - offset + 2 || recordLength > data.size - offset) {
|
|
237
|
+
throw IOException("Invalid PAX record length")
|
|
368
238
|
}
|
|
369
|
-
|
|
239
|
+
val recordEnd = offset + recordLength
|
|
240
|
+
if (data[recordEnd - 1] != '\n'.code.toByte()) throw IOException("Invalid PAX record")
|
|
241
|
+
val equals = data.indexOf('='.code.toByte(), separator + 1, recordEnd - 1)
|
|
242
|
+
if (equals <= separator + 1) throw IOException("Invalid PAX record")
|
|
243
|
+
val key = decodeUtf8(data, separator + 1, equals - separator - 1)
|
|
244
|
+
if (headers.containsKey(key)) throw IOException("Duplicate PAX key: $key")
|
|
245
|
+
headers[key] = decodeUtf8(data, equals + 1, recordEnd - equals - 2)
|
|
370
246
|
offset = recordEnd
|
|
371
247
|
}
|
|
372
|
-
|
|
373
|
-
return path
|
|
248
|
+
return headers
|
|
374
249
|
}
|
|
375
250
|
|
|
376
|
-
private fun
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
end: Int,
|
|
380
|
-
): Int {
|
|
381
|
-
var result = 0
|
|
382
|
-
for (index in start until end) {
|
|
383
|
-
val digit = bytes[index].toInt() - '0'.code
|
|
384
|
-
if (digit !in 0..9 || result > (Int.MAX_VALUE - digit) / 10) {
|
|
385
|
-
throw IOException("Invalid PAX record length")
|
|
386
|
-
}
|
|
387
|
-
result = result * 10 + digit
|
|
251
|
+
private fun parsePaxSize(value: String): Long {
|
|
252
|
+
if (value.isEmpty() || value.any { it !in '0'..'9' }) {
|
|
253
|
+
throw IOException("Invalid PAX entry size")
|
|
388
254
|
}
|
|
389
|
-
return
|
|
255
|
+
return value.toLongOrNull() ?: throw IOException("Invalid PAX entry size")
|
|
390
256
|
}
|
|
391
257
|
|
|
392
|
-
private fun
|
|
258
|
+
private fun decodeUtf8(
|
|
393
259
|
bytes: ByteArray,
|
|
260
|
+
offset: Int,
|
|
261
|
+
length: Int,
|
|
262
|
+
): String =
|
|
263
|
+
try {
|
|
264
|
+
Charsets.UTF_8
|
|
265
|
+
.newDecoder()
|
|
266
|
+
.onMalformedInput(CodingErrorAction.REPORT)
|
|
267
|
+
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
|
268
|
+
.decode(ByteBuffer.wrap(bytes, offset, length))
|
|
269
|
+
.toString()
|
|
270
|
+
} catch (error: Exception) {
|
|
271
|
+
throw IOException("Invalid UTF-8 in TAR metadata", error)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private fun ByteArray.indexOf(
|
|
394
275
|
value: Byte,
|
|
395
276
|
start: Int,
|
|
396
|
-
|
|
277
|
+
endExclusive: Int = size,
|
|
397
278
|
): Int {
|
|
398
|
-
for (index in start until
|
|
399
|
-
if (bytes[index] == value) return index
|
|
400
|
-
}
|
|
279
|
+
for (index in start until endExclusive) if (this[index] == value) return index
|
|
401
280
|
return -1
|
|
402
281
|
}
|
|
403
282
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
private fun skipPadding(size: Long) {
|
|
408
|
-
val remainder = size % BLOCK_SIZE
|
|
409
|
-
if (remainder != 0L) {
|
|
410
|
-
skipBytes(BLOCK_SIZE - remainder)
|
|
283
|
+
private fun readExtension(size: Long): ByteArray {
|
|
284
|
+
if (size < 0 || size > MAX_EXTENSION_SIZE) {
|
|
285
|
+
throw IOException("TAR extension is too large")
|
|
411
286
|
}
|
|
287
|
+
val data = ByteArray(size.toInt())
|
|
288
|
+
readExactly(data)
|
|
289
|
+
readPadding(size)
|
|
290
|
+
return data
|
|
412
291
|
}
|
|
413
292
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
while (remaining > 0) {
|
|
422
|
-
val toSkip = minOf(buffer.size.toLong(), remaining).toInt()
|
|
423
|
-
val skipped = input.read(buffer, 0, toSkip)
|
|
424
|
-
if (skipped < 0) throw EOFException("Unexpected end of stream")
|
|
425
|
-
remaining -= skipped
|
|
293
|
+
private fun readExactly(buffer: ByteArray) {
|
|
294
|
+
var offset = 0
|
|
295
|
+
while (offset < buffer.size) {
|
|
296
|
+
val count = input.read(buffer, offset, buffer.size - offset)
|
|
297
|
+
if (count < 0) throw EOFException("Unexpected end of TAR extension")
|
|
298
|
+
if (count == 0) throw IOException("TAR extension read made no progress")
|
|
299
|
+
offset += count
|
|
426
300
|
}
|
|
427
301
|
}
|
|
428
302
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (entry.size > MAX_FILE_SIZE) {
|
|
439
|
-
throw SecurityException("File size ${entry.size} exceeds maximum $MAX_FILE_SIZE")
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Check for absolute paths
|
|
443
|
-
if (entry.name.startsWith("/")) {
|
|
444
|
-
throw SecurityException("Absolute path not allowed: ${entry.name}")
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Check for path traversal
|
|
448
|
-
val normalized = entry.name.replace('\\', '/')
|
|
449
|
-
if (normalized.contains("../") ||
|
|
450
|
-
normalized.contains("/..") ||
|
|
451
|
-
normalized == ".." ||
|
|
452
|
-
normalized.startsWith("../")
|
|
453
|
-
) {
|
|
454
|
-
throw SecurityException("Path traversal detected: ${entry.name}")
|
|
455
|
-
}
|
|
303
|
+
private fun readPadding(size: Long) {
|
|
304
|
+
val remainder = size % BLOCK_SIZE
|
|
305
|
+
if (remainder == 0L) return
|
|
306
|
+
val padding = ByteArray((BLOCK_SIZE - remainder).toInt())
|
|
307
|
+
readExactly(padding)
|
|
308
|
+
if (!padding.all { it == 0.toByte() }) throw IOException("Invalid TAR entry padding")
|
|
309
|
+
}
|
|
456
310
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
311
|
+
private fun skipExactly(count: Long) {
|
|
312
|
+
var remaining = count
|
|
313
|
+
val buffer = ByteArray(8192)
|
|
314
|
+
while (remaining > 0) {
|
|
315
|
+
val read = input.read(buffer, 0, minOf(buffer.size.toLong(), remaining).toInt())
|
|
316
|
+
if (read < 0) throw EOFException("Unexpected end of TAR archive")
|
|
317
|
+
if (read == 0) throw IOException("TAR skip made no progress")
|
|
318
|
+
remaining -= read
|
|
460
319
|
}
|
|
461
320
|
}
|
|
462
321
|
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* TAR archive entry
|
|
466
|
-
*/
|
|
467
|
-
data class TarArchiveEntry(
|
|
468
|
-
var name: String,
|
|
469
|
-
val mode: Int,
|
|
470
|
-
val size: Long,
|
|
471
|
-
val typeFlag: Char,
|
|
472
|
-
val linkName: String,
|
|
473
|
-
) {
|
|
474
|
-
val isDirectory: Boolean
|
|
475
|
-
get() = typeFlag == '5' || name.endsWith('/')
|
|
476
|
-
|
|
477
|
-
val isFile: Boolean
|
|
478
|
-
get() = typeFlag == '0' || typeFlag == '\u0000'
|
|
479
|
-
|
|
480
|
-
val isSymbolicLink: Boolean
|
|
481
|
-
get() = typeFlag == '2'
|
|
482
|
-
}
|