// Copyright (c) 2021 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information import Foundation 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. Integers can also be encoded as non-decimal based number. This is controlled by `radix` parameter. */ 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..> (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..