mirror of
https://github.com/tsolomko/SWCompression.git
synced 2026-06-23 14:56:41 +00:00
94 lines
3.3 KiB
Swift
94 lines
3.3 KiB
Swift
// Copyright (c) 2026 Timofey Solomko
|
|
// Licensed under MIT License
|
|
//
|
|
// See LICENSE for license information
|
|
|
|
import BitByteData
|
|
|
|
extension LittleEndianByteReader {
|
|
|
|
/**
|
|
Reads a `String` field from TAR container. The end of the field is defined by either:
|
|
1. NULL character (thus CString in the name of the function).
|
|
2. Reaching specified maximum length.
|
|
|
|
Strings are encoded in TAR using ASCII encoding. We are treating them as UTF-8 encoded instead since UTF-8 is
|
|
backwards compatible with ASCII.
|
|
|
|
We use `String(cString:)` initalizer because TAR's NULL-ending ASCII fields are basically CStrings (especially,
|
|
since we are treating them as UTF-8 strings). As a bonus, this initializer is not failable: it replaces unparsable
|
|
as UTF-8 sequences of bytes with UTF-8 Replacement Character, so we don't need to throw any error.
|
|
*/
|
|
func tarCString(maxLength: Int) -> String {
|
|
var buffer = self.bytes(count: maxLength)
|
|
if buffer.last != 0 {
|
|
buffer.append(0)
|
|
}
|
|
return buffer.withUnsafeBufferPointer { String(cString: $0.baseAddress!) }
|
|
}
|
|
|
|
/**
|
|
Reads an `Int` field from TAR container. The end of the field is defined by either:
|
|
1. NULL or SPACE (in containers created by certain old implementations) character.
|
|
2. Reaching specified maximum length.
|
|
|
|
Integers are encoded in TAR as ASCII text. We are treating them as UTF-8 encoded strings since UTF-8 is backwards
|
|
compatible with ASCII.
|
|
*/
|
|
func tarInt(maxLength: Int) -> Int? {
|
|
guard maxLength > 0
|
|
else { return nil }
|
|
|
|
var buffer = [UInt8]()
|
|
buffer.reserveCapacity(maxLength)
|
|
|
|
let firstByte = self.byte()
|
|
self.offset -= 1
|
|
|
|
if firstByte & 0x80 != 0 { // Base-256 encoding; used for big numeric fields.
|
|
buffer = self.bytes(count: maxLength)
|
|
/// Inversion mask for handling negative numbers.
|
|
let invMask = buffer[0] & 0x40 != 0 ? 0xFF : 0x00
|
|
var result = 0
|
|
for i in 0..<buffer.count {
|
|
var byte = buffer[i].toInt() ^ invMask
|
|
if i == 0 {
|
|
byte &= 0x7F // Ignoring bit which indicates base-256 encoding.
|
|
}
|
|
if result >> (Int.bitWidth - 8) > 0 {
|
|
return nil // Integer overflow
|
|
}
|
|
result = (result << 8) | byte
|
|
}
|
|
if result >> (Int.bitWidth - 1) > 0 {
|
|
return nil // Integer overflow
|
|
}
|
|
return invMask == 0xFF ? ~result : result
|
|
}
|
|
|
|
// Normal, octal encoding.
|
|
let startOffset = self.offset
|
|
|
|
// Skip leading NULLs and whitespaces (used by some implementations).
|
|
var byte: UInt8
|
|
var actualStartIndex = 0
|
|
repeat {
|
|
byte = self.byte()
|
|
actualStartIndex += 1
|
|
} while (byte == 0 || byte == 0x20) && (actualStartIndex < maxLength)
|
|
self.offset -= 1
|
|
|
|
for _ in actualStartIndex..<maxLength {
|
|
let byte = self.byte()
|
|
guard byte != 0 && byte != 0x20
|
|
else { break }
|
|
buffer.append(byte)
|
|
}
|
|
self.offset = startOffset + maxLength
|
|
guard let string = String(bytes: buffer, encoding: .utf8)
|
|
else { return nil }
|
|
return Int(string, radix: 8)
|
|
}
|
|
|
|
}
|