Merge branch 'develop' into release-3.0.0

This commit is contained in:
Timofey Solomko
2017-05-30 21:22:33 +03:00
24 changed files with 463 additions and 320 deletions
+4
View File
@@ -20,6 +20,10 @@ v3.0.0
- In CocoaPods configurations availability of such support depends on
the presence of corresponding podspecs.
v2.4.3
----------------
- Fixed incorrect calculation of header's checksum for GZip archives.
v2.4.2
----------------
- Fixed a problem, where ZipEntry.fileName was returning fileComment instead.
+6 -2
View File
@@ -91,6 +91,7 @@
069DCEA21EB64D0300ADC374 /* test7.answer in Resources */ = {isa = PBXBuildFile; fileRef = 069DCE991EB64D0300ADC374 /* test7.answer */; };
069DCEA31EB64D0300ADC374 /* test8.answer in Resources */ = {isa = PBXBuildFile; fileRef = 069DCE9A1EB64D0300ADC374 /* test8.answer */; };
069DCEA41EB64D0300ADC374 /* test9.answer in Resources */ = {isa = PBXBuildFile; fileRef = 069DCE9B1EB64D0300ADC374 /* test9.answer */; };
069F27701EDA0D5E00736269 /* TestUnicode.zip in Resources */ = {isa = PBXBuildFile; fileRef = 069F276F1EDA0D5E00736269 /* TestUnicode.zip */; };
06A393391DE0709300182E12 /* GzipArchive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063364E21DC52979007E313F /* GzipArchive.swift */; };
06A3933A1DE0709300182E12 /* ZlibArchive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064492581DC606D400F10981 /* ZlibArchive.swift */; };
06A3933B1DE0709300182E12 /* Deflate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061FCE251DBCC4BE0052F7BE /* Deflate.swift */; };
@@ -205,6 +206,7 @@
069DCE991EB64D0300ADC374 /* test7.answer */ = {isa = PBXFileReference; lastKnownFileType = file; path = test7.answer; sourceTree = "<group>"; };
069DCE9A1EB64D0300ADC374 /* test8.answer */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = test8.answer; sourceTree = "<group>"; };
069DCE9B1EB64D0300ADC374 /* test9.answer */ = {isa = PBXFileReference; lastKnownFileType = file; path = test9.answer; sourceTree = "<group>"; };
069F276F1EDA0D5E00736269 /* TestUnicode.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = TestUnicode.zip; sourceTree = "<group>"; };
06A3933E1DE070B500182E12 /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = "<group>"; };
06A3933F1DE070B500182E12 /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
06A393401DE070B500182E12 /* Info-watchOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-watchOS.plist"; sourceTree = "<group>"; };
@@ -377,6 +379,7 @@
06BD0D1F1E3D0C630054AA5C /* ZIP */ = {
isa = PBXGroup;
children = (
069F276F1EDA0D5E00736269 /* TestUnicode.zip */,
06BD0D201E3D0C630054AA5C /* SWCompressionSourceCode.zip */,
06BD0D211E3D0C630054AA5C /* TestDataDescriptor.zip */,
06BD0D221E3D0C630054AA5C /* TestZip64.zip */,
@@ -433,10 +436,10 @@
06CC187E1DE35607003532F5 /* DeflateTests.swift */,
0680E1B21DDA3D0A005C05EB /* GzipTests.swift */,
0680E1B01DDA2D7C005C05EB /* ZlibTests.swift */,
061FCE2C1DBCC6A30052F7BE /* Constants.swift */,
0675183C1E2A883600D16354 /* ZipTests.swift */,
06BE1AD81DB410F100EE0F59 /* Info.plist */,
0696E5C51ED0B22F00921D4D /* TarTests.swift */,
061FCE2C1DBCC6A30052F7BE /* Constants.swift */,
06BE1AD81DB410F100EE0F59 /* Info.plist */,
);
path = Tests;
sourceTree = "<group>";
@@ -748,6 +751,7 @@
06CC187D1DE35269003532F5 /* test.zlib in Resources */,
06D07A9B1EB64E7A00BC8C65 /* test3.bz2 in Resources */,
069DCE9C1EB64D0300ADC374 /* test1.answer in Resources */,
069F27701EDA0D5E00736269 /* TestUnicode.zip in Resources */,
069AC2181E02DE490041AC13 /* test8.lzma in Resources */,
068B36791ED0AC050016269E /* full_test.tar in Resources */,
069DCE8F1EB64B1500ADC374 /* test7.gz in Resources */,
+7 -4
View File
@@ -50,7 +50,7 @@ public enum BZip2Error: Error {
}
/// Provides function to decompress data, which were compressed using BZip2.
public final class BZip2: DecompressionAlgorithm {
public class BZip2: DecompressionAlgorithm {
/**
Decompresses `compressedData` with BZip2 algortihm.
@@ -94,12 +94,14 @@ public final class BZip2: DecompressionAlgorithm {
if blockType == 0x314159265359 {
let blockBytes = try decode(data: &pointerData)
guard CheckSums.bzip2CRC32(blockBytes) == blockCRC32 else { throw BZip2Error.wrongCRC(Data(bytes: out)) }
guard CheckSums.bzip2CRC32(blockBytes) == blockCRC32
else { throw BZip2Error.wrongCRC(Data(bytes: out)) }
for byte in blockBytes {
out.append(byte)
}
} else if blockType == 0x177245385090 {
guard CheckSums.bzip2CRC32(out) == blockCRC32 else { throw BZip2Error.wrongCRC(Data(bytes: out)) }
guard CheckSums.bzip2CRC32(out) == blockCRC32
else { throw BZip2Error.wrongCRC(Data(bytes: out)) }
break
} else {
throw BZip2Error.wrongBlockType
@@ -187,7 +189,8 @@ public final class BZip2: DecompressionAlgorithm {
}
let tables = try computeTables()
var favourites = try used.enumerated().reduce([]) { (partialResult: [UInt8], next: (offset: Int, element: Bool)) throws -> [UInt8] in
var favourites = try used.enumerated().reduce([]) {
(partialResult: [UInt8], next: (offset: Int, element: Bool)) throws -> [UInt8] in
if next.element {
var newResult = partialResult
newResult.append(next.offset.toUInt8())
+1 -1
View File
@@ -8,7 +8,7 @@
import Foundation
final class BitToByteWriter {
class BitToByteWriter {
private(set) var buffer: [UInt8] = []
private var bitMask: UInt8
+1 -1
View File
@@ -13,7 +13,7 @@ enum BitOrder {
case reversed
}
final class DataWithPointer {
class DataWithPointer {
let bitOrder: BitOrder
let size: Int
+15 -11
View File
@@ -29,7 +29,7 @@ public enum DeflateError: Error {
}
/// Provides function to decompress data, which were compressed with DEFLATE.
public final class Deflate: DecompressionAlgorithm {
public class Deflate: DecompressionAlgorithm {
private struct Constants {
static let codeLengthOrders: [Int] =
@@ -135,7 +135,8 @@ public final class Deflate: DecompressionAlgorithm {
let codeLengthsLength = pointerData.intFromBits(count: 4) + 4
// Read code lengths codes.
// Moreover, they are stored in a very specific order (defined by HuffmanTree.Constants.codeLengthOrders).
// Moreover, they are stored in a very specific order,
// defined by HuffmanTree.Constants.codeLengthOrders.
var lengthsForOrder = Array(repeating: 0, count: 19)
for i in 0..<codeLengthsLength {
lengthsForOrder[Constants.codeLengthOrders[i]] = pointerData.intFromBits(count: 3)
@@ -182,8 +183,10 @@ public final class Deflate: DecompressionAlgorithm {
}
// We have read codeLengths for both trees at once.
// Now we need to split them and make corresponding trees.
mainLiterals = HuffmanTree(lengthsToOrder: Array(codeLengths[0..<literals]), &pointerData)
mainDistances = HuffmanTree(lengthsToOrder: Array(codeLengths[literals..<codeLengths.count]), &pointerData)
mainLiterals = HuffmanTree(lengthsToOrder: Array(codeLengths[0..<literals]),
&pointerData)
mainDistances = HuffmanTree(lengthsToOrder: Array(codeLengths[literals..<codeLengths.count]),
&pointerData)
}
// Main loop of data decompression.
@@ -205,7 +208,8 @@ public final class Deflate: DecompressionAlgorithm {
// which we need to add to nextSymbol to get the full length.
let extraLength = (257 <= nextSymbol && nextSymbol <= 260) || nextSymbol == 285 ?
0 : (((nextSymbol - 257) >> 2) - 1)
// Actually, nextSymbol is not a starting value of length but an index for special array of starting values.
// Actually, nextSymbol is not a starting value of length,
// but an index for special array of starting values.
let length = Constants.lengthBase[nextSymbol - 257] +
pointerData.intFromBits(count: extraLength)
@@ -314,7 +318,6 @@ public final class Deflate: DecompressionAlgorithm {
// Since `length` of uncompressed block is 16-bit integer,
// there is a limitation on size of uncompressed block.
// Falling back to static Huffman encoding in case of big uncompressed block is a band-aid solution.
// TODO: Implement spliting uncompressed block into smaller blocks.
if uncompBlockSize <= staticHuffmanBlockSize && uncompBlockSize <= 65535 {
// If according to our calculations static huffman will not make output smaller than input,
// we fallback to creating uncompressed block.
@@ -377,18 +380,18 @@ public final class Deflate: DecompressionAlgorithm {
for code in bldCodes {
switch code {
case .byte(let byte):
mainLiterals.code(symbol: byte.toInt(), &bitWriter)
try mainLiterals.code(symbol: byte.toInt(), &bitWriter, DeflateError.symbolNotFound)
case .lengthDistance(let ld):
mainLiterals.code(symbol: ld.lengthSymbol, &bitWriter)
try mainLiterals.code(symbol: ld.lengthSymbol, &bitWriter, DeflateError.symbolNotFound)
bitWriter.write(number: ld.lengthExtraBits, bitsCount: ld.lengthExtraBitsCount)
mainDistances.code(symbol: ld.distanceSymbol, &bitWriter)
try mainDistances.code(symbol: ld.distanceSymbol, &bitWriter, DeflateError.symbolNotFound)
bitWriter.write(number: ld.distanceExtraBits, bitsCount: ld.distanceExtraBitsCount)
}
}
// End data symbol.
mainLiterals.code(symbol: 256, &bitWriter)
try mainLiterals.code(symbol: 256, &bitWriter, DeflateError.symbolNotFound)
bitWriter.finish()
return bitWriter.buffer
@@ -432,7 +435,8 @@ public final class Deflate: DecompressionAlgorithm {
case .byte(let byte):
return "raw symbol: \(byte)"
case .lengthDistance(let ld):
return "length: \(ld.length), length symbol: \(ld.lengthSymbol), distance: \(ld.distance), distance symbol: \(ld.distanceSymbol)"
return "length: \(ld.length), length symbol: \(ld.lengthSymbol), " +
"distance: \(ld.distance), distance symbol: \(ld.distanceSymbol)"
}
}
}
+89 -25
View File
@@ -37,6 +37,8 @@ public enum GzipError: Error {
case wrongCRC(Data)
/// Computed isize didn't match the value stored in the archive.
case wrongISize
case cannotEncodeISOLatin1
}
/// A structure which provides information about gzip archive.
@@ -66,7 +68,7 @@ public struct GzipHeader {
case ntfs = 11
/// File system was unknown to the archiver.
case unknown = 255
/// File system was one of the rare systems..
/// File system was one of the rare systems.
case other = 256
}
@@ -81,6 +83,8 @@ public struct GzipHeader {
/// Comment inside the archive.
public let comment: String?
public let isTextFile: Bool
/**
Initializes the structure with the values of first 'member' in gzip archive presented in `archiveData`.
@@ -91,7 +95,6 @@ public struct GzipHeader {
- Throws: `GzipError`. It may indicate that either the data is damaged or
it might not be compressed with gzip at all.
*/
public init(archiveData: Data) throws {
let pointerData = DataWithPointer(data: archiveData, bitOrder: .reversed)
try self.init(pointerData)
@@ -126,6 +129,8 @@ public struct GzipHeader {
self.osType = FileSystemType(rawValue: pointerData.alignedByte().toInt()) ?? .other
headerBytes.append(self.osType.rawValue.toUInt8())
self.isTextFile = flags & Flags.ftext != 0
// Some archives may contain extra fields
if flags & Flags.fextra != 0 {
let xlen = pointerData.intFromAlignedBytes(count: 2)
@@ -142,9 +147,9 @@ public struct GzipHeader {
var fnameBytes: [UInt8] = []
while true {
let byte = pointerData.alignedByte()
headerBytes.append(byte)
guard byte != 0 else { break }
fnameBytes.append(byte)
headerBytes.append(byte)
}
self.originalFileName = String(data: Data(fnameBytes), encoding: .utf8)
} else {
@@ -156,9 +161,9 @@ public struct GzipHeader {
var fcommentBytes: [UInt8] = []
while true {
let byte = pointerData.alignedByte()
headerBytes.append(byte)
guard byte != 0 else { break }
fcommentBytes.append(byte)
headerBytes.append(byte)
}
self.comment = String(data: Data(fcommentBytes), encoding: .utf8)
} else {
@@ -177,7 +182,7 @@ public struct GzipHeader {
}
/// Provides unarchive function for GZip archives.
public final class GzipArchive: Archive {
public class GzipArchive: Archive {
/**
Unarchives gzip archive stored in `archiveData`.
@@ -199,23 +204,17 @@ public final class GzipArchive: Archive {
/// Object with input data which supports convenient work with bit shifts.
var pointerData = DataWithPointer(data: data, bitOrder: .reversed)
var out: [UInt8] = []
_ = try GzipHeader(pointerData)
while !pointerData.isAtTheEnd {
_ = try GzipHeader(pointerData)
let memberData = Data(bytes: try Deflate.decompress(&pointerData))
let memberData = try Deflate.decompress(&pointerData)
let crc32 = pointerData.uint32FromAlignedBytes(count: 4)
guard CheckSums.crc32(memberData) == crc32 else { throw GzipError.wrongCRC(memberData) }
let crc32 = pointerData.uint32FromAlignedBytes(count: 4)
guard CheckSums.crc32(memberData) == crc32 else { throw GzipError.wrongCRC(Data(bytes: out)) }
let isize = pointerData.intFromAlignedBytes(count: 4)
guard UInt64(memberData.count) % UInt64(1) << 32 == UInt64(isize) else { throw GzipError.wrongISize }
let isize = pointerData.intFromAlignedBytes(count: 4)
guard UInt64(memberData.count) % UInt64(1) << 32 == UInt64(isize) else { throw GzipError.wrongISize }
out.append(contentsOf: memberData)
}
return Data(bytes: out)
return memberData
}
/**
@@ -234,16 +233,81 @@ public final class GzipArchive: Archive {
- Returns: Data object with resulting archive.
*/
public static func archive(data: Data) throws -> Data {
let out: [UInt8] = [
public static func archive(data: Data, comment: String? = nil, fileName: String? = nil,
writeHeaderCRC: Bool = false, isTextFile: Bool = false,
osType: GzipHeader.FileSystemType? = nil, modificationTime: Date? = nil) throws -> Data {
var flags: UInt8 = 0
var commentData = Data()
if var comment = comment {
flags |= 1 << 4
if comment.characters.last != "\u{00}" {
comment.append("\u{00}")
}
if let data = comment.data(using: .isoLatin1) {
commentData = data
} else {
throw GzipError.cannotEncodeISOLatin1
}
}
var fileNameData = Data()
if var fileName = fileName {
flags |= 1 << 3
if fileName.characters.last != "\u{00}" {
fileName.append("\u{00}")
}
if let data = fileName.data(using: .isoLatin1) {
fileNameData = data
} else {
throw GzipError.cannotEncodeISOLatin1
}
}
if writeHeaderCRC {
flags |= 1 << 1
}
if isTextFile {
flags |= 1 << 0
}
var os: UInt8 = 255
if let osType = osType {
os = (osType == .other ? 255 : osType.rawValue).toUInt8()
}
var mtimeBytes: [UInt8] = [0, 0, 0, 0]
if let modificationTime = modificationTime {
let timeInterval = Int(modificationTime.timeIntervalSince1970)
for i in 0..<4 {
mtimeBytes[i] = UInt8(truncatingBitPattern: (timeInterval & (0xFF << (i * 8))) >> (i * 8))
}
}
var headerBytes: [UInt8] = [
0x1f, 0x8b, // 'magic' bytes.
8, // Compression method (DEFLATE).
0, // Flags; currently no flags are set.
0, 0, 0, 0, // Mtime; currently timestamp is not set.
2, // Extra flags; 2 means that DEFLATE used slowest algorithm.
255, // OS Type; set to default value (unknown).
flags // Flags; currently no flags are set.
]
var outData = Data(bytes: out)
for i in 0..<4 {
headerBytes.append(mtimeBytes[i])
}
headerBytes.append(2) // Extra flags; 2 means that DEFLATE used slowest algorithm.
headerBytes.append(os)
var outData = Data(bytes: headerBytes)
outData.append(fileNameData)
outData.append(commentData)
if writeHeaderCRC {
let headerCRC = CheckSums.crc32(outData)
for i: UInt32 in 0..<2 {
outData.append(UInt8((headerCRC & (0xFF << (i * 8))) >> (i * 8)))
}
}
outData.append(try Deflate.compress(data: data))
let crc32 = CheckSums.crc32(data)
+6 -6
View File
@@ -156,8 +156,8 @@ class HuffmanTree {
}
}
func code(symbol: Int, _ bitWriter: inout BitToByteWriter) {
precondition(self.coding, "Tree is not initalized for coding!")
func code(symbol: Int, _ bitWriter: inout BitToByteWriter, _ symbolNotFoundError: Error) throws {
precondition(self.coding, "HuffmanTree is not initalized for coding!")
var index = 0
while true {
@@ -166,9 +166,9 @@ class HuffmanTree {
if foundSymbol == symbol {
return
} else {
fatalError("Symbol not found, this error should be replaced with Error.")
throw symbolNotFoundError
}
case .branch(_):
case .branch:
let leftChildIndex = 2 * index + 1
if leftChildIndex < self.leafCount {
switch self.tree[leftChildIndex] {
@@ -200,7 +200,7 @@ class HuffmanTree {
bitWriter.write(bit: 1)
continue
} else {
fatalError("Symbol not found, this error should be replaced with Error.")
throw symbolNotFoundError
}
case .branch(let rightArray):
if rightArray.contains(symbol) {
@@ -208,7 +208,7 @@ class HuffmanTree {
bitWriter.write(bit: 1)
continue
} else {
fatalError("Symbol not found, this error should be replaced with Error.")
throw symbolNotFoundError
}
}
}
+5 -23
View File
@@ -38,10 +38,12 @@ public enum LZMAError: Error {
case repeatWillExceed
/// The amount of already decoded bytes is smaller than repeat length.
case notEnoughToRepeat
case decoderIsNotInitialised
}
/// Provides function to decompress data, which were compressed with LZMA
public final class LZMA: DecompressionAlgorithm {
public class LZMA: DecompressionAlgorithm {
/**
Decompresses `compressedData` with LZMA algortihm.
@@ -63,28 +65,8 @@ public final class LZMA: DecompressionAlgorithm {
}
static func decompress(_ pointerData: inout DataWithPointer) throws -> [UInt8] {
// Firstly, we need to parse LZMA properties.
var properties = pointerData.alignedByte()
if properties >= (9 * 5 * 5) {
throw LZMAError.wrongProperties
}
/// The number of literal context bits
let lc = properties % 9
properties /= 9
/// The number of pos bits
let pb = properties / 5
/// The number of literal pos bits
let lp = properties % 5
var dictionarySize = pointerData.intFromAlignedBytes(count: 4)
dictionarySize = dictionarySize < (1 << 12) ? 1 << 12 : dictionarySize
/// Size of uncompressed data. -1 means it is unknown/undefined.
var uncompressedSize = pointerData.intFromAlignedBytes(count: 8)
uncompressedSize = Double(uncompressedSize) == pow(Double(2), Double(64)) - 1 ? -1 : uncompressedSize
let lzmaDecoder = try LZMADecoder(&pointerData, lc, pb, lp, dictionarySize)
try lzmaDecoder.decodeLZMA(&uncompressedSize)
let lzmaDecoder = try LZMADecoder(&pointerData)
try lzmaDecoder.decodeLZMA()
return lzmaDecoder.out
}
+2 -2
View File
@@ -35,7 +35,7 @@ public enum LZMA2Error: Error {
}
/// Provides function to decompress data, which were compressed with LZMA2
public final class LZMA2: DecompressionAlgorithm {
public class LZMA2: DecompressionAlgorithm {
/**
Decompresses `compressedData` with LZMA2 algortihm. LZMA2 is a modification of LZMA.
@@ -60,7 +60,7 @@ public final class LZMA2: DecompressionAlgorithm {
static func decompress(_ dictionarySize: Int, _ pointerData: inout DataWithPointer) throws -> [UInt8] {
// At this point lzmaDecoder will be in a VERY bad state.
let lzmaDecoder = try LZMADecoder(&pointerData, 0, 0, 0, 0)
let lzmaDecoder = try LZMADecoder(&pointerData)
try lzmaDecoder.decodeLZMA2(dictionarySize)
return lzmaDecoder.out
}
+1 -1
View File
@@ -9,7 +9,7 @@
import Foundation
/// Used to decode symbols that need several bits for storing.
final class LZMABitTreeDecoder {
class LZMABitTreeDecoder {
private var pointerData: DataWithPointer
+58 -51
View File
@@ -24,16 +24,16 @@ struct LZMAConstants {
// LZMAConstants.numStates << LZMAConstants.numPosBitsMax = 192
}
final class LZMADecoder {
class LZMADecoder {
private var pointerData: DataWithPointer
private var lc: UInt8
private var lp: UInt8
private var pb: UInt8
private var dictionarySize: Int
private var lc: UInt8 = 0
private var lp: UInt8 = 0
private var pb: UInt8 = 0
private var dictionarySize: Int = 0
private var rangeDecoder: LZMARangeDecoder
private var rangeDecoder: LZMARangeDecoder = LZMARangeDecoder()
private var posSlotDecoder: [LZMABitTreeDecoder] = []
private var alignDecoder: LZMABitTreeDecoder
private var lenDecoder: LZMALenDecoder
@@ -43,7 +43,7 @@ final class LZMADecoder {
For literal decoding we need `1 << (lc + lp)` amount of tables.
Each table contains 0x300 probabilities.
*/
private var literalProbs: [[Int]]
private var literalProbs: [[Int]] = []
/**
Array with all probabilities:
@@ -55,9 +55,9 @@ final class LZMADecoder {
- 229..<241: isRepG2
- 241..<433: isRep0Long
*/
private var probabilities: [Int] = Array(repeating: LZMAConstants.probInitValue, count: 2 * 192 + 4 * 12)
private var probabilities: [Int] = []
private var posDecoders: [Int]
private var posDecoders: [Int] = []
// 'Distance history table'.
private var rep0: Int = 0
@@ -74,29 +74,13 @@ final class LZMADecoder {
private var dictStart = 0
private var dictEnd = 0
init(_ pointerData: inout DataWithPointer, _ lc: UInt8, _ pb: UInt8, _ lp: UInt8,
_ dictionarySize: Int) throws {
/// For proper processing of LZMA data `resetState` and `resetProperties` functions should be called at least once.
/// If that has happened, then stateReset is true.
private var stateReset: Bool = false
init(_ pointerData: inout DataWithPointer) throws {
self.pointerData = pointerData
self.lc = lc
self.lp = lp
self.pb = pb
self.dictionarySize = dictionarySize
self.rangeDecoder = LZMARangeDecoder()
self.literalProbs = Array(repeating: Array(repeating: LZMAConstants.probInitValue,
count: 0x300),
count: 1 << (lc + lp).toInt())
self.posSlotDecoder = []
for _ in 0..<LZMAConstants.numLenToPosStates {
self.posSlotDecoder.append(LZMABitTreeDecoder(numBits: 6, &self.pointerData))
}
self.alignDecoder = LZMABitTreeDecoder(numBits: LZMAConstants.numAlignBits, &self.pointerData)
self.posDecoders = Array(repeating: LZMAConstants.probInitValue,
count: 1 + LZMAConstants.numFullDistances - LZMAConstants.endPosModelIndex)
// There are two types of matches so we need two decoders for them.
self.lenDecoder = LZMALenDecoder(&self.pointerData)
self.repLenDecoder = LZMALenDecoder(&self.pointerData)
@@ -104,6 +88,11 @@ final class LZMADecoder {
// MARK: LZMA2 related functions.
private func resetDictionary(_ dictSize: Int) {
self.dictionarySize = dictSize
self.dictStart = self.dictEnd
}
private func resetProperties() throws {
var properties = pointerData.alignedByte()
if properties >= (9 * 5 * 5) {
@@ -116,11 +105,9 @@ final class LZMADecoder {
self.pb = properties / 5
/// The number of literal pos bits
self.lp = properties % 5
}
private func resetDictionary(_ dictSize: Int) {
self.dictionarySize = dictSize
self.dictStart = self.dictEnd
// We need to 'reset state' because several properties of Decoder depend on the values of lc, lp, pb.
self.resetState()
}
private func resetState() {
@@ -145,17 +132,15 @@ final class LZMADecoder {
count: 1 + LZMAConstants.numFullDistances - LZMAConstants.endPosModelIndex)
self.lenDecoder = LZMALenDecoder(&self.pointerData)
self.repLenDecoder = LZMALenDecoder(&self.pointerData)
self.stateReset = true
}
private func decodeUncompressed() {
let dataSize = self.pointerData.alignedByte().toInt() << 8 + self.pointerData.alignedByte().toInt() + 1
for _ in 0..<dataSize {
let byte = pointerData.alignedByte()
out.append(byte)
dictEnd += 1
if dictEnd - dictStart == dictionarySize {
dictStart += 1
}
self.put(byte)
}
}
@@ -174,11 +159,9 @@ final class LZMADecoder {
self.resetState()
case 2:
try self.resetProperties()
self.resetState()
dataStartIndex += 1
case 3:
try self.resetProperties()
self.resetState()
dataStartIndex += 1
self.resetDictionary(dictSize)
default:
@@ -186,12 +169,12 @@ final class LZMADecoder {
}
var uncompressedSize = unpackSize
let startCount = out.count
try decodeLZMA(&uncompressedSize)
try decode(&uncompressedSize)
guard unpackSize == out.count - startCount && pointerData.index - dataStartIndex == compressedSize
else { throw LZMA2Error.wrongSizes }
}
// MARK: Main LZMA 2 decoder function.
// MARK: Main LZMA 2 (format) decoder function.
func decodeLZMA2(_ lzma2DictionarySize: Int) throws {
mainLoop: while true {
@@ -214,9 +197,28 @@ final class LZMADecoder {
}
}
// MARK: Main LZMA decoder function.
// MARK: Main LZMA (format) decoder function.
func decodeLZMA() throws {
// Firstly, we need to parse LZMA properties.
try self.resetProperties()
let dictSize = pointerData.intFromAlignedBytes(count: 4)
dictionarySize = dictSize < (1 << 12) ? 1 << 12 : dictSize
/// Size of uncompressed data. -1 means it is unknown/undefined.
var uncompressedSize = pointerData.intFromAlignedBytes(count: 8)
uncompressedSize = Double(uncompressedSize) == pow(Double(2), Double(64)) - 1 ? -1 : uncompressedSize
try decode(&uncompressedSize)
}
// MARK: Main LZMA (algorithm) decoder function.
private func decode(_ uncompressedSize: inout Int) throws {
guard stateReset else {
throw LZMAError.decoderIsNotInitialised
}
func decodeLZMA(_ uncompressedSize: inout Int) throws {
// First, we need to initialize Rande Decoder.
guard let rD = LZMARangeDecoder(&self.pointerData) else {
throw LZMAError.rangeDecoderInitError
@@ -233,7 +235,8 @@ final class LZMADecoder {
}
let posState = out.count & ((1 << pb.toInt()) - 1)
if rangeDecoder.decode(bitWithProb: &probabilities[(state << LZMAConstants.numPosBitsMax) + posState]) == 0 {
if rangeDecoder.decode(bitWithProb:
&probabilities[(state << LZMAConstants.numPosBitsMax) + posState]) == 0 {
if uncompressedSize == 0 { throw LZMAError.exceededUncompressedSize }
// DECODE LITERAL:
@@ -258,7 +261,8 @@ final class LZMADecoder {
repeat {
let matchBit = ((matchByte >> 7) & 1).toInt()
matchByte <<= 1
let bit = rangeDecoder.decode(bitWithProb: &literalProbs[litState][((1 + matchBit) << 8) + symbol])
let bit = rangeDecoder.decode(bitWithProb:
&literalProbs[litState][((1 + matchBit) << 8) + symbol])
symbol = (symbol << 1) | bit
if matchBit != bit {
break
@@ -292,12 +296,13 @@ final class LZMADecoder {
if dictEnd == 0 { throw LZMAError.windowIsEmpty }
if rangeDecoder.decode(bitWithProb: &probabilities[205 + state]) == 0 {
// (We use last distance from 'distance history table').
if rangeDecoder.decode(bitWithProb: &probabilities[241 + (state << LZMAConstants.numPosBitsMax) + posState]) == 0 {
if rangeDecoder.decode(bitWithProb:
&probabilities[241 + (state << LZMAConstants.numPosBitsMax) + posState]) == 0 {
// SHORT REP MATCH CASE
state = state < 7 ? 9 : 11
let byte = self.byte(at: rep0 + 1)
uncompressedSize -= 1
self.put(byte)
uncompressedSize -= 1
continue
}
} else { // REP MATCH CASE
@@ -373,15 +378,17 @@ final class LZMADecoder {
}
if uncompressedSize == 0 { throw LZMAError.exceededUncompressedSize }
if rep0 >= dictionarySize || (rep0 > dictEnd && dictEnd < dictionarySize) { throw LZMAError.notEnoughToRepeat }
if rep0 >= dictionarySize || (rep0 > dictEnd && dictEnd < dictionarySize) {
throw LZMAError.notEnoughToRepeat
}
}
// Converting from zero-based length of the match to the real one.
len += LZMAConstants.matchMinLen
if uncompressedSize > -1 && uncompressedSize < len { throw LZMAError.repeatWillExceed }
for _ in 0..<len {
let byte = self.byte(at: rep0 + 1)
uncompressedSize -= 1
self.put(byte)
uncompressedSize -= 1
}
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
import Foundation
final class LZMALenDecoder {
class LZMALenDecoder {
private var pointerData: DataWithPointer
+1 -1
View File
@@ -8,7 +8,7 @@
import Foundation
final class LZMARangeDecoder {
class LZMARangeDecoder {
private var pointerData: DataWithPointer
+52 -94
View File
@@ -164,107 +164,65 @@ public class TarEntry: ContainerEntry {
fileNamePrefix = nil
}
if let headerString = globalExtendedHeader {
let headerEntries = headerString.components(separatedBy: "\n")
for headerEntry in headerEntries {
if headerEntry == "" {
continue
}
let headerEntrySplit = headerEntry.characters.split(separator: " ", maxSplits: 1,
omittingEmptySubsequences: false)
guard Int(String(headerEntrySplit[0])) == headerEntry.characters.count + 1
else { throw TarError.wrongPaxHeaderEntry }
let keywordValue = String(headerEntrySplit[1])
let keywordValueSplit = keywordValue.characters.split(separator: "=", maxSplits: 1,
omittingEmptySubsequences: false)
let keyword = String(keywordValueSplit[0])
let value = String(keywordValueSplit[1])
switch keyword {
case "atime":
if let interval = Double(value) {
self.accessTime = Date(timeIntervalSince1970: interval)
func parseHeader(_ header: String?, _ fieldsDict: inout [String : String]) throws {
if let headerString = header {
let headerEntries = headerString.components(separatedBy: "\n")
for headerEntry in headerEntries {
if headerEntry == "" {
continue
}
case "charset":
self.charset = value
case "mtime":
if let interval = Double(value) {
self.modificationTime = Date(timeIntervalSince1970: interval)
}
case "comment":
self.comment = value
case "gid":
self.groupID = Int(value)
case "gname":
self.ownerGroupName = value
case "hdrcharset":
break
case "linkpath":
self.linkPath = value
case "path":
self.paxPath = value
case "size":
if let intValue = Int(value) {
self.size = intValue
}
case "uid":
self.ownerID = Int(value)
case "uname":
self.ownerUserName = value
default:
self.unknownExtendedHeaderEntries[keyword] = value
let headerEntrySplit = headerEntry.characters.split(separator: " ", maxSplits: 1,
omittingEmptySubsequences: false)
guard Int(String(headerEntrySplit[0])) == headerEntry.characters.count + 1
else { throw TarError.wrongPaxHeaderEntry }
let keywordValue = String(headerEntrySplit[1])
let keywordValueSplit = keywordValue.characters.split(separator: "=", maxSplits: 1,
omittingEmptySubsequences: false)
let keyword = String(keywordValueSplit[0])
let value = String(keywordValueSplit[1])
fieldsDict[keyword] = value
}
}
}
if let headerString = localExtendedHeader {
let headerEntries = headerString.components(separatedBy: "\n")
for headerEntry in headerEntries {
if headerEntry == "" {
continue
var fieldsDict = [String: String]()
try parseHeader(globalExtendedHeader, &fieldsDict)
try parseHeader(localExtendedHeader, &fieldsDict)
for (keyword, value) in fieldsDict {
switch keyword {
case "atime":
if let interval = Double(value) {
self.accessTime = Date(timeIntervalSince1970: interval)
}
let headerEntrySplit = headerEntry.characters.split(separator: " ", maxSplits: 1,
omittingEmptySubsequences: false)
guard Int(String(headerEntrySplit[0])) == headerEntry.characters.count + 1
else { throw TarError.wrongPaxHeaderEntry }
let keywordValue = String(headerEntrySplit[1])
let keywordValueSplit = keywordValue.characters.split(separator: "=", maxSplits: 1,
omittingEmptySubsequences: false)
let keyword = String(keywordValueSplit[0])
let value = String(keywordValueSplit[1])
switch keyword {
case "atime":
if let interval = Double(value) {
self.accessTime = Date(timeIntervalSince1970: interval)
}
case "charset":
self.charset = value
case "mtime":
if let interval = Double(value) {
self.modificationTime = Date(timeIntervalSince1970: interval)
}
case "comment":
self.comment = value
case "gid":
self.groupID = Int(value)
case "gname":
self.ownerGroupName = value
case "hdrcharset":
break
case "linkpath":
self.linkPath = value
case "path":
self.paxPath = value
case "size":
if let intValue = Int(value) {
self.size = intValue
}
case "uid":
self.ownerID = Int(value)
case "uname":
self.ownerUserName = value
default:
self.unknownExtendedHeaderEntries[keyword] = value
case "charset":
self.charset = value
case "mtime":
if let interval = Double(value) {
self.modificationTime = Date(timeIntervalSince1970: interval)
}
case "comment":
self.comment = value
case "gid":
self.groupID = Int(value)
case "gname":
self.ownerGroupName = value
case "hdrcharset":
break
case "linkpath":
self.linkPath = value
case "path":
self.paxPath = value
case "size":
if let intValue = Int(value) {
self.size = intValue
}
case "uid":
self.ownerID = Int(value)
case "uname":
self.ownerUserName = value
default:
self.unknownExtendedHeaderEntries[keyword] = value
}
}
+104 -74
View File
@@ -55,7 +55,7 @@ public enum XZError: Error {
}
/// Provides unarchive function for XZ archives.
public final class XZArchive: Archive {
public class XZArchive: Archive {
/**
Unarchives xz archive stored in `archiveData`.
@@ -78,81 +78,120 @@ public final class XZArchive: Archive {
var pointerData = DataWithPointer(data: data, bitOrder: .reversed)
var out: [UInt8] = []
streamLoop: while !pointerData.isAtTheEnd {
// STREAM HEADER
let streamHeader = try processStreamHeader(&pointerData)
// BLOCKS AND INDEX
/// Zero value of blockHeaderSize means that we encountered INDEX.
var blockInfos: [(unpaddedSize: Int, uncompSize: Int)] = []
var indexSize = -1
while true {
let blockHeaderSize = pointerData.alignedByte()
if blockHeaderSize == 0 {
indexSize = try processIndex(blockInfos, &pointerData)
break
// First, we should check footer magic bytes.
// If they are wrong, then file cannot be 'undamaged'.
// But the file may end with padding, so we need to account for this.
pointerData.index = pointerData.size - 1
var paddingBytes = 0
while true {
let byte = pointerData.alignedByte()
if byte != 0 {
if paddingBytes % 4 != 0 {
throw XZError.wrongPadding
} else {
let blockInfo = try processBlock(blockHeaderSize, &pointerData)
out.append(contentsOf: blockInfo.blockData)
let checkSize: Int
switch streamHeader.checkType {
case 0x00:
checkSize = 0
break
case 0x01:
checkSize = 4
let check = pointerData.uint32FromAlignedBytes(count: 4)
guard CheckSums.crc32(blockInfo.blockData) == check
else { throw XZError.wrongCheck(Data(bytes: out)) }
case 0x04:
checkSize = 8
let check = pointerData.uint64FromAlignedBytes(count: 8)
guard CheckSums.crc64(blockInfo.blockData) == check
else { throw XZError.wrongCheck(Data(bytes: out)) }
case 0x0A:
throw XZError.checkTypeSHA256
default:
throw XZError.fieldReservedValue
}
blockInfos.append((blockInfo.unpaddedSize + checkSize, blockInfo.uncompressedSize))
break
}
}
// STREAM FOOTER
try processFooter(streamHeader, indexSize, &pointerData)
guard !pointerData.isAtTheEnd else { break streamLoop }
// STREAM PADDING
var paddingBytes = 0
while true {
let byte = pointerData.alignedByte()
if byte != 0 {
if paddingBytes % 4 != 0 {
throw XZError.wrongPadding
} else {
break
}
}
paddingBytes += 1
}
pointerData.index -= 1
paddingBytes += 1
pointerData.index -= 2
}
pointerData.index -= 2
guard pointerData.alignedBytes(count: 2) == [0x59, 0x5A]
else { throw XZError.wrongMagic }
// Let's now go to the start of the file.
pointerData.index = 0
// streamLoop: while !pointerData.isAtTheEnd {
// STREAM HEADER
let streamHeader = try processStreamHeader(&pointerData)
// BLOCKS AND INDEX
/// Zero value of blockHeaderSize means that we encountered INDEX.
var blockInfos: [(unpaddedSize: Int, uncompSize: Int)] = []
var indexSize = -1
while true {
let blockHeaderSize = pointerData.alignedByte()
if blockHeaderSize == 0 {
indexSize = try processIndex(blockInfos, &pointerData)
break
} else {
let blockInfo = try processBlock(blockHeaderSize, &pointerData)
out.append(contentsOf: blockInfo.blockData)
let checkSize: Int
switch streamHeader.checkType {
case 0x00:
checkSize = 0
break
case 0x01:
checkSize = 4
let check = pointerData.uint32FromAlignedBytes(count: 4)
guard CheckSums.crc32(blockInfo.blockData) == check
else { throw XZError.wrongCheck(Data(bytes: out)) }
case 0x04:
checkSize = 8
let check = pointerData.uint64FromAlignedBytes(count: 8)
guard CheckSums.crc64(blockInfo.blockData) == check
else { throw XZError.wrongCheck(Data(bytes: out)) }
case 0x0A:
throw XZError.checkTypeSHA256
default:
throw XZError.fieldReservedValue
}
blockInfos.append((blockInfo.unpaddedSize + checkSize, blockInfo.uncompressedSize))
}
}
// STREAM FOOTER
try processFooter(streamHeader, indexSize, &pointerData)
// guard !pointerData.isAtTheEnd else { break streamLoop }
//
// // STREAM PADDING
// paddingBytes = 0
// while true {
// let byte = pointerData.alignedByte()
// if byte != 0 {
// if paddingBytes % 4 != 0 {
// throw XZError.wrongPadding
// } else {
// break
// }
// }
// if pointerData.isAtTheEnd {
// if byte != 0 || paddingBytes % 4 != 3 {
// throw XZError.wrongPadding
// } else {
// break streamLoop
// }
// }
// paddingBytes += 1
// }
// pointerData.index -= 1
// }
return Data(bytes: out)
}
private static func processStreamHeader(_ pointerData: inout DataWithPointer) throws -> (checkType: Int, flagsCRC: UInt32) {
private static func processStreamHeader(_ pointerData: inout DataWithPointer) throws -> (checkType: UInt8, flagsCRC: UInt32) {
// Check magic number.
guard pointerData.uint64FromAlignedBytes(count: 6) == 0x005A587A37FD
else { throw XZError.wrongMagic }
// First byte of flags must be equal to zero.
guard pointerData.alignedByte() == 0
let flagsBytes = pointerData.alignedBytes(count: 2)
// First, we need to check for corruption in flags,
// so we compare CRC32 of flags to the value stored in archive.
let flagsCRC = pointerData.uint32FromAlignedBytes(count: 4)
guard CheckSums.crc32(flagsBytes) == flagsCRC
else { throw XZError.wrongInfoCRC }
// If data is not corrupted, then some bits must be equal to zero.
guard flagsBytes[0] == 0 && flagsBytes[1] & 0xF0 == 0
else { throw XZError.fieldReservedValue }
// Next four bits indicate type of redundancy check.
let checkType = pointerData.intFromBits(count: 4)
// Four bits of second flags byte indicate type of redundancy check.
let checkType = flagsBytes[1] & 0x0F
switch checkType {
case 0x00, 0x01, 0x04, 0x0A:
break
@@ -160,20 +199,11 @@ public final class XZArchive: Archive {
throw XZError.fieldReservedValue
}
// Final four bits must be equal to zero.
guard pointerData.intFromBits(count: 4) == 0
else { throw XZError.fieldReservedValue }
// CRC-32 of flags must be equal to the value in archive.
let flagsCRC = pointerData.uint32FromAlignedBytes(count: 4)
guard CheckSums.crc32([0, checkType.toUInt8()]) == flagsCRC
else { throw XZError.wrongInfoCRC }
return (checkType, flagsCRC)
}
private static func processBlock(_ blockHeaderSize: UInt8,
_ pointerData: inout DataWithPointer) throws -> (blockData: [UInt8], unpaddedSize: Int, uncompressedSize: Int) {
_ pointerData: inout DataWithPointer) throws -> (blockData: [UInt8], unpaddedSize: Int, uncompressedSize: Int) {
var blockBytes: [UInt8] = []
let blockHeaderStartIndex = pointerData.index - 1
blockBytes.append(blockHeaderSize)
@@ -311,7 +341,7 @@ public final class XZArchive: Archive {
return indexBytes.count + 4
}
private static func processFooter(_ streamHeader: (checkType: Int, flagsCRC: UInt32),
private static func processFooter(_ streamHeader: (checkType: UInt8, flagsCRC: UInt32),
_ indexSize: Int,
_ pointerData: inout DataWithPointer) throws {
let footerCRC = pointerData.uint32FromAlignedBytes(count: 4)
@@ -333,7 +363,7 @@ public final class XZArchive: Archive {
// Flags in the footer should be the same as in the header.
guard footerStreamFlags[0] == 0
else { throw XZError.fieldReservedValue }
guard footerStreamFlags[1] & 0x0F == streamHeader.checkType.toUInt8()
guard footerStreamFlags[1] & 0x0F == streamHeader.checkType
else { throw XZError.wrongArchiveInfo }
guard footerStreamFlags[1] & 0xF0 == 0
else { throw XZError.fieldReservedValue }
+23 -10
View File
@@ -81,7 +81,14 @@ public class ZipEntry: ContainerEntry {
Particularly, it is true if size of data is 0 and last character of entry's name is '/'.
*/
public var isDirectory: Bool {
return self.size == 0 && self.name.characters.last == "/"
let hostSystem = (cdEntry.versionMadeBy & 0xFF00) >> 8
if hostSystem == 0 || hostSystem == 3 { // MS-DOS or UNIX case.
// In both of this cases external file attributes indicate if this is a directory.
// This is indicated by a special bit in the lowest byte of attributes.
return cdEntry.externalFileAttributes & 0x10 != 0
} else {
return size == 0 && name.characters.last == "/"
}
}
/**
@@ -155,9 +162,9 @@ public class ZipEntry: ContainerEntry {
}
// Now, let's update from CD with values from data descriptor.
crc32 = pointerData.uint32FromAlignedBytes(count: 4)
compSize = Int(pointerData.uint32FromAlignedBytes(count: 4))
uncompSize = Int(pointerData.uint32FromAlignedBytes(count: 4))
// TODO: It may be ZIP64 Data Descriptor.
let sizeOfSizeField: UInt32 = localHeader.zip64FieldsArePresent ? 8 : 4
compSize = Int(pointerData.uint32FromAlignedBytes(count: sizeOfSizeField))
uncompSize = Int(pointerData.uint32FromAlignedBytes(count: sizeOfSizeField))
}
guard compSize == realCompSize && uncompSize == fileBytes.count
@@ -168,7 +175,7 @@ public class ZipEntry: ContainerEntry {
return Data(bytes: fileBytes)
}
init(_ cdEntry: CentralDirectoryEntry, _ pointerData: inout DataWithPointer) {
fileprivate init(_ cdEntry: CentralDirectoryEntry, _ pointerData: inout DataWithPointer) {
self.cdEntry = cdEntry
self.pointerData = pointerData
}
@@ -186,9 +193,10 @@ public class ZipContainer: Container {
It is likely that directories will be encountered earlier than files stored in those directories,
but one SHOULD NOT assume that this is the case.
- Note: Currently, there is no universal (platform and file system independent) method to determine if entry is a directory.
One can check this by looking at the size of entry's data (it should be 0 for directory) AND
the last character of entry's name (it should be '/'). If all of these is true then entry is likely to be a directory.
- Note: Currently, there is no universal (platform and file system independent) method to determine,
if entry is a directory. One can check this by looking at the size of entry's data
(it should be 0 for directory) AND the last character of entry's name (it should be '/').
If all of these is true then entry is likely to be a directory.
- Parameter containerData: Data of ZIP container.
@@ -245,6 +253,8 @@ struct LocalHeader {
private(set) var compSize: UInt64
private(set) var uncompSize: UInt64
private(set) var zip64FieldsArePresent: Bool = false
let fileName: String
init(_ pointerData: inout DataWithPointer) throws {
@@ -286,6 +296,8 @@ struct LocalHeader {
// In local header both uncompressed size and compressed size fields are required.
self.uncompSize = pointerData.uint64FromAlignedBytes(count: 8)
self.compSize = pointerData.uint64FromAlignedBytes(count: 8)
self.zip64FieldsArePresent = true
default:
pointerData.index += size
}
@@ -452,7 +464,10 @@ struct CentralDirectoryEntry {
struct EndOfCentralDirectory {
/// Number of the current disk.
private(set) var currentDiskNumber: UInt32
/// Number of the disk with the start of CD.
private(set) var cdDiskNumber: UInt32
private(set) var cdEntries: UInt64
private(set) var cdSize: UInt64
@@ -462,9 +477,7 @@ struct EndOfCentralDirectory {
/// Indicates if Zip64 records should be present.
var zip64RecordExists = false
/// Number of current disk.
self.currentDiskNumber = pointerData.uint32FromAlignedBytes(count: 2)
/// Number of the disk with the start of CD.
self.cdDiskNumber = pointerData.uint32FromAlignedBytes(count: 2)
guard self.currentDiskNumber == self.cdDiskNumber
else { throw ZipError.multiVolumesNotSupported }
+2 -2
View File
@@ -122,7 +122,7 @@ public struct ZlibHeader {
}
/// Provides unarchive function for Zlib archives.
public final class ZlibArchive: Archive {
public class ZlibArchive: Archive {
/**
Unarchives Zlib archive stored in `archiveData`.
@@ -171,7 +171,7 @@ public final class ZlibArchive: Archive {
public static func archive(data: Data) throws -> Data {
let out: [UInt8] = [
120, // CM (Compression Method) = 8 (DEFLATE), CINFO (Compression Info) = 7 (32K window size).
218, // Flags: slowest algorithm, no preset dictionary.
218 // Flags: slowest algorithm, no preset dictionary.
]
var outData = Data(bytes: out)
outData.append(try Deflate.compress(data: data))
+50 -3
View File
@@ -44,9 +44,7 @@ class GzipTests: XCTestCase {
}
// Test GZip unarchiving.
let decompressedData = try? GzipArchive.unarchive(archiveData: archiveData)
guard decompressedData != nil else {
guard let decompressedData = try? GzipArchive.unarchive(archiveData: archiveData) else {
XCTFail("Failed to decompress")
return
}
@@ -66,6 +64,51 @@ class GzipTests: XCTestCase {
#endif
}
func archive(test testName: String) {
// Load answer data.
guard let answerData = try? Data(contentsOf: Constants.url(forAnswer: testName)) else {
XCTFail("Failed to get the answer")
return
}
// Options for archiving.
let mtimeDate = Date(timeIntervalSinceNow: 0.0)
let mtime = Double(Int(mtimeDate.timeIntervalSince1970))
// Test GZip archiving.
guard let archiveData = try? GzipArchive.archive(data: answerData,
comment: "some file comment",
fileName: testName + ".answer",
writeHeaderCRC: true,
isTextFile: true,
osType: .macintosh,
modificationTime: mtimeDate) else {
XCTFail("Failed to create archive.")
return
}
// Test output GZip header.
guard let testGzipHeader = try? GzipHeader(archiveData: archiveData) else {
XCTFail("Failed to get archive header")
return
}
XCTAssertEqual(testGzipHeader.compressionMethod, .deflate, "Incorrect compression method")
XCTAssertEqual(testGzipHeader.modificationTime?.timeIntervalSince1970, mtime, "Incorrect mtime")
XCTAssertEqual(testGzipHeader.osType, .macintosh, "Incorrect os type")
XCTAssertEqual(testGzipHeader.originalFileName, "\(testName).answer", "Incorrect original file name")
XCTAssertEqual(testGzipHeader.comment, "some file comment", "Incorrect comment")
XCTAssertTrue(testGzipHeader.isTextFile)
// Test output GZip archive content.
guard let decompressedData = try? GzipArchive.unarchive(archiveData: archiveData) else {
XCTFail("Failed to decompress")
return
}
XCTAssertEqual(decompressedData, answerData, "Decompression was incorrect")
}
func testGzip1() {
self.header(test: "test1", mtime: 1482698300)
self.unarchive(test: "test1")
@@ -111,4 +154,8 @@ class GzipTests: XCTestCase {
self.unarchive(test: "test9")
}
func testGzipArchive4() {
self.archive(test: "test4")
}
}
+5 -2
View File
@@ -48,7 +48,10 @@ class TarTests: XCTestCase {
XCTAssertEqual(result.count, 5)
for entry in result {
let tarEntry = entry as! TarEntry
guard let tarEntry = entry as? TarEntry else {
XCTFail("Unable to convert entry to TarEntry.")
return
}
let name = tarEntry.name.components(separatedBy: ".")[0]
guard let answerData = try? Data(contentsOf: Constants.url(forAnswer: name)) else {
XCTFail("Failed to get the answer")
@@ -56,7 +59,7 @@ class TarTests: XCTestCase {
}
XCTAssertEqual(tarEntry.data(), answerData)
XCTAssertEqual(tarEntry.isDirectory, false)
XCTAssert(tarEntry.accessTime != nil)
XCTAssertNotNil(tarEntry.accessTime)
}
}
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9e7d47eb06f830a510511db4480a355a82428a324035be9ca24926537b281fed
size 96
oid sha256:11bd10a5b044b914097caaf218b57258d693f7c4943c52e6b8602267abe69490
size 100
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fcbef95b205c0ef54a4c4da590429b2b0ae4ef334476dac5abe971cf59d0542f
size 340
+1
View File
@@ -56,6 +56,7 @@ class XZTests: XCTestCase {
}
func testXz4() {
// This test contains padding!
self.perform(test: "test4")
}
+24 -4
View File
@@ -69,11 +69,31 @@ class ZipTests: XCTestCase {
return
}
for entry in entries {
if !entry.isDirectory {
XCTAssertNotNil(try? entry.data())
}
for entry in entries where !entry.isDirectory {
XCTAssertNotNil(try? entry.data())
}
}
func testUnicode() {
guard let testData = try? Data(contentsOf: Constants.url(forTest: "TestUnicode",
withType: ZipTests.testType),
options: .mappedIfSafe) else {
XCTFail("Failed to load test archive")
return
}
guard let entries = try? ZipContainer.open(containerData: testData) else {
XCTFail("Unable to open ZIP archive.")
return
}
guard entries.count == 1 else {
XCTFail("Incorrect number of entries.")
return
}
XCTAssertEqual(entries[0].name, "текстовый файл")
XCTAssertEqual(entries[0].isDirectory, false)
}
}