mirror of
https://github.com/tsolomko/SWCompression.git
synced 2026-06-23 14:56:41 +00:00
Added first prototype of TAR parsing. And a test for it.
This commit is contained in:
@@ -3,3 +3,4 @@
|
||||
*.answer filter=lfs diff=lfs merge=lfs -text
|
||||
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.tar filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
0681B3DF1E44F674001A9D79 /* BitToByteWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0681B3DD1E44F674001A9D79 /* BitToByteWriter.swift */; };
|
||||
0681B3E01E44F674001A9D79 /* BitToByteWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0681B3DD1E44F674001A9D79 /* BitToByteWriter.swift */; };
|
||||
0681B3E11E44F674001A9D79 /* BitToByteWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0681B3DD1E44F674001A9D79 /* BitToByteWriter.swift */; };
|
||||
0691B9401EBE3E100037EE58 /* test.tar in Resources */ = {isa = PBXBuildFile; fileRef = 0691B93F1EBE3E100037EE58 /* test.tar */; };
|
||||
06991C701E05A9F70030FF07 /* test9.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 06991C6D1E05A9F70030FF07 /* test9.lzma */; };
|
||||
06991C711E05A9F70030FF07 /* test10.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 06991C6E1E05A9F70030FF07 /* test10.lzma */; };
|
||||
06991C721E05A9F70030FF07 /* test11.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 06991C6F1E05A9F70030FF07 /* test11.lzma */; };
|
||||
@@ -96,6 +97,11 @@
|
||||
06A393441DE0713300182E12 /* SWCompression.h in Headers */ = {isa = PBXBuildFile; fileRef = 06A393421DE070B500182E12 /* SWCompression.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
06A393451DE0713A00182E12 /* SWCompression.h in Headers */ = {isa = PBXBuildFile; fileRef = 06A393421DE070B500182E12 /* SWCompression.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
06A393461DE0714000182E12 /* SWCompression.h in Headers */ = {isa = PBXBuildFile; fileRef = 06A393421DE070B500182E12 /* SWCompression.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
06ADB2D91EBCEFA60053A188 /* TarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ADB2D81EBCEFA60053A188 /* TarContainer.swift */; };
|
||||
06ADB2DA1EBCEFA60053A188 /* TarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ADB2D81EBCEFA60053A188 /* TarContainer.swift */; };
|
||||
06ADB2DB1EBCEFA60053A188 /* TarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ADB2D81EBCEFA60053A188 /* TarContainer.swift */; };
|
||||
06ADB2DC1EBCEFA60053A188 /* TarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ADB2D81EBCEFA60053A188 /* TarContainer.swift */; };
|
||||
06ADB2E01EBCF33C0053A188 /* TarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06ADB2DF1EBCF33C0053A188 /* TarTests.swift */; };
|
||||
06B374CB1E0EECD500064853 /* random_file.zlib in Resources */ = {isa = PBXBuildFile; fileRef = 06B374CA1E0EECD500064853 /* random_file.zlib */; };
|
||||
06BB8F301E0C2C720079FB9E /* LZMA2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BB8F2F1E0C2C720079FB9E /* LZMA2.swift */; };
|
||||
06BB8F311E0C2C720079FB9E /* LZMA2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BB8F2F1E0C2C720079FB9E /* LZMA2.swift */; };
|
||||
@@ -167,6 +173,7 @@
|
||||
0680E1B21DDA3D0A005C05EB /* GzipTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GzipTests.swift; sourceTree = "<group>"; };
|
||||
0680E1B41DDA4075005C05EB /* Bzip2Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bzip2Tests.swift; sourceTree = "<group>"; };
|
||||
0681B3DD1E44F674001A9D79 /* BitToByteWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitToByteWriter.swift; path = Sources/BitToByteWriter.swift; sourceTree = SOURCE_ROOT; };
|
||||
0691B93F1EBE3E100037EE58 /* test.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = test.tar; sourceTree = "<group>"; };
|
||||
06991C6D1E05A9F70030FF07 /* test9.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = test9.lzma; sourceTree = "<group>"; };
|
||||
06991C6E1E05A9F70030FF07 /* test10.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = test10.lzma; sourceTree = "<group>"; };
|
||||
06991C6F1E05A9F70030FF07 /* test11.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = test11.lzma; sourceTree = "<group>"; };
|
||||
@@ -200,6 +207,8 @@
|
||||
06A393401DE070B500182E12 /* Info-watchOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-watchOS.plist"; sourceTree = "<group>"; };
|
||||
06A393411DE070B500182E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
06A393421DE070B500182E12 /* SWCompression.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SWCompression.h; sourceTree = "<group>"; };
|
||||
06ADB2D81EBCEFA60053A188 /* TarContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TarContainer.swift; sourceTree = "<group>"; };
|
||||
06ADB2DF1EBCF33C0053A188 /* TarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TarTests.swift; sourceTree = "<group>"; };
|
||||
06B374CA1E0EECD500064853 /* random_file.zlib */ = {isa = PBXFileReference; lastKnownFileType = file; path = random_file.zlib; sourceTree = "<group>"; };
|
||||
06BB8F2F1E0C2C720079FB9E /* LZMA2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LZMA2.swift; sourceTree = "<group>"; };
|
||||
06BD0D201E3D0C630054AA5C /* SWCompressionSourceCode.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = SWCompressionSourceCode.zip; sourceTree = "<group>"; };
|
||||
@@ -274,6 +283,14 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0691B93E1EBE3E100037EE58 /* TAR */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0691B93F1EBE3E100037EE58 /* test.tar */,
|
||||
);
|
||||
path = TAR;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
069AC2161E02DE490041AC13 /* LZMA */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -397,6 +414,7 @@
|
||||
061FCE251DBCC4BE0052F7BE /* Deflate.swift */,
|
||||
06FED40B1DD7717E0013DFB2 /* BZip2.swift */,
|
||||
067518351E2A86F700D16354 /* ZipContainer.swift */,
|
||||
06ADB2D81EBCEFA60053A188 /* TarContainer.swift */,
|
||||
06A3933D1DE070B500182E12 /* Service */,
|
||||
);
|
||||
path = Sources;
|
||||
@@ -414,6 +432,7 @@
|
||||
0680E1B01DDA2D7C005C05EB /* ZlibTests.swift */,
|
||||
061FCE2C1DBCC6A30052F7BE /* Constants.swift */,
|
||||
0675183C1E2A883600D16354 /* ZipTests.swift */,
|
||||
06ADB2DF1EBCF33C0053A188 /* TarTests.swift */,
|
||||
06BE1AD81DB410F100EE0F59 /* Info.plist */,
|
||||
);
|
||||
path = Tests;
|
||||
@@ -428,6 +447,7 @@
|
||||
069DCE7F1EB64B1500ADC374 /* GZip */,
|
||||
069AC2161E02DE490041AC13 /* LZMA */,
|
||||
06BD0D1F1E3D0C630054AA5C /* ZIP */,
|
||||
0691B93E1EBE3E100037EE58 /* TAR */,
|
||||
06CC185F1DE35269003532F5 /* test.zlib */,
|
||||
06B374CA1E0EECD500064853 /* random_file.zlib */,
|
||||
);
|
||||
@@ -689,6 +709,7 @@
|
||||
069DCE9D1EB64D0300ADC374 /* test2.answer in Resources */,
|
||||
06D07AB01EB64EE800BC8C65 /* test5.xz in Resources */,
|
||||
06BD0D241E3D0C630054AA5C /* TestDataDescriptor.zip in Resources */,
|
||||
0691B9401EBE3E100037EE58 /* test.tar in Resources */,
|
||||
06D07A9E1EB64E7A00BC8C65 /* test6.bz2 in Resources */,
|
||||
06D07A9C1EB64E7A00BC8C65 /* test4.bz2 in Resources */,
|
||||
06D07AA01EB64E7A00BC8C65 /* test8.bz2 in Resources */,
|
||||
@@ -757,6 +778,7 @@
|
||||
06FED40D1DD7717E0013DFB2 /* BZip2.swift in Sources */,
|
||||
069D0FF61E0D6CD000D8AA87 /* LZMABitTreeDecoder.swift in Sources */,
|
||||
065569681DC65C2A00A47E66 /* GzipArchive.swift in Sources */,
|
||||
06ADB2DA1EBCEFA60053A188 /* TarContainer.swift in Sources */,
|
||||
06E2346D1E068E9600F18798 /* XZArchive.swift in Sources */,
|
||||
063DF10B1DE1F07800F38082 /* DataWithPointer.swift in Sources */,
|
||||
);
|
||||
@@ -782,6 +804,7 @@
|
||||
065569711DC65C2B00A47E66 /* Deflate.swift in Sources */,
|
||||
069D0FF71E0D6CD000D8AA87 /* LZMABitTreeDecoder.swift in Sources */,
|
||||
06FED40E1DD7717E0013DFB2 /* BZip2.swift in Sources */,
|
||||
06ADB2DB1EBCEFA60053A188 /* TarContainer.swift in Sources */,
|
||||
06E2346E1E068E9600F18798 /* XZArchive.swift in Sources */,
|
||||
0655696F1DC65C2B00A47E66 /* GzipArchive.swift in Sources */,
|
||||
);
|
||||
@@ -807,6 +830,7 @@
|
||||
06FED40F1DD7717E0013DFB2 /* BZip2.swift in Sources */,
|
||||
069D0FF81E0D6CD000D8AA87 /* LZMABitTreeDecoder.swift in Sources */,
|
||||
065569761DC65C2B00A47E66 /* GzipArchive.swift in Sources */,
|
||||
06ADB2DC1EBCEFA60053A188 /* TarContainer.swift in Sources */,
|
||||
06E2346F1E068E9600F18798 /* XZArchive.swift in Sources */,
|
||||
063DF10C1DE1F07800F38082 /* DataWithPointer.swift in Sources */,
|
||||
);
|
||||
@@ -832,6 +856,7 @@
|
||||
06A3933B1DE0709300182E12 /* Deflate.swift in Sources */,
|
||||
069D0FF51E0D6CD000D8AA87 /* LZMABitTreeDecoder.swift in Sources */,
|
||||
06A3933C1DE0709300182E12 /* BZip2.swift in Sources */,
|
||||
06ADB2D91EBCEFA60053A188 /* TarContainer.swift in Sources */,
|
||||
06E2346C1E068E9600F18798 /* XZArchive.swift in Sources */,
|
||||
063DF10A1DE1F07800F38082 /* DataWithPointer.swift in Sources */,
|
||||
);
|
||||
@@ -847,6 +872,7 @@
|
||||
069AC2121E02DDAF0041AC13 /* LzmaTests.swift in Sources */,
|
||||
0680E1B11DDA2D7C005C05EB /* ZlibTests.swift in Sources */,
|
||||
0680E1B31DDA3D0A005C05EB /* GzipTests.swift in Sources */,
|
||||
06ADB2E01EBCF33C0053A188 /* TarTests.swift in Sources */,
|
||||
06E234801E069ABD00F18798 /* XzTests.swift in Sources */,
|
||||
0675183D1E2A883600D16354 /* ZipTests.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -192,7 +192,7 @@ final class LZMADecoder {
|
||||
}
|
||||
|
||||
// MARK: Main LZMA 2 decoder function.
|
||||
|
||||
|
||||
func decodeLZMA2(_ lzma2DictionarySize: Int) throws {
|
||||
mainLoop: while true {
|
||||
let controlByte = pointerData.alignedByte()
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
//
|
||||
// TarContainer.swift
|
||||
// SWCompression
|
||||
//
|
||||
// Created by Timofey Solomko on 05.05.17.
|
||||
// Copyright © 2017 Timofey Solomko. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
Error happened during processing TAR archive (container).
|
||||
It may indicate that either the data is damaged or it might not be TAR archive (container) at all.
|
||||
|
||||
- `error`: error description.
|
||||
*/
|
||||
public enum TarError: Error {
|
||||
case TooSmallFileIsPassed
|
||||
case FieldIsNotNumber
|
||||
case WrongHeaderChecksum
|
||||
case WrongUstarVersion
|
||||
}
|
||||
|
||||
public class TarContainer {
|
||||
|
||||
enum TarType {
|
||||
case prePOSIX
|
||||
case ustar // aka POSIX.
|
||||
case pax // TODO: Implement
|
||||
}
|
||||
|
||||
public static func files(from data: Data) throws -> [Data] {
|
||||
// First, if the TAR container contains only header, it should be at least 512 bytes long.
|
||||
// So we have to check this.
|
||||
guard data.count >= 512 else { throw TarError.TooSmallFileIsPassed }
|
||||
|
||||
// Then, we need to find out, if the container is POSIX or not.
|
||||
// POSIX containers have a 'ustar\x00' (or 'ustar\x20') indicator at offset 257.
|
||||
let posixIndicator = String(data: data.subdata(in: 257..<263), encoding: .ascii)
|
||||
// if posixIndicator == "ustar\u{00}" || posixIndicator == "ustar\u{20}" {
|
||||
// return try parseUstar(data)
|
||||
// } else {
|
||||
// return try parsePrePosix(data)
|
||||
// }
|
||||
|
||||
var output = [Data]()
|
||||
|
||||
var index = 0
|
||||
var blockStartIndex = 0
|
||||
while true {
|
||||
// Container ends with two zero-filled records.
|
||||
if data.subdata(in: index..<index + 1024) == Data(bytes: Array(repeating: 0, count: 1024)) {
|
||||
break
|
||||
}
|
||||
|
||||
// File name
|
||||
let fileName = data.nullEndedAsciiString(index, 100)
|
||||
index += 100
|
||||
|
||||
// File mode
|
||||
let fileMode = Int(data.nullSpaceEndedAsciiString(index, 8)!)
|
||||
index += 8
|
||||
|
||||
// Owner's user ID
|
||||
let ownerID = Int(data.nullSpaceEndedAsciiString(index, 8)!)
|
||||
index += 8
|
||||
|
||||
// Group's user ID
|
||||
let groupID = Int(data.nullSpaceEndedAsciiString(index, 8)!)
|
||||
index += 8
|
||||
|
||||
// File size
|
||||
guard let octalFileSize = Int(data.nullSpaceEndedAsciiString(index, 12)!)
|
||||
else { throw TarError.FieldIsNotNumber }
|
||||
let fileSize = octalToDecimal(octalFileSize)
|
||||
index += 12
|
||||
|
||||
// Modification time
|
||||
guard let octalMtime = Int(data.nullSpaceEndedAsciiString(index, 12)!)
|
||||
else { throw TarError.FieldIsNotNumber }
|
||||
let mtime = octalToDecimal(octalMtime)
|
||||
index += 12
|
||||
|
||||
// Checksum
|
||||
guard let octalChecksum = Int(data.nullSpaceEndedAsciiString(index, 8)!)
|
||||
else { throw TarError.FieldIsNotNumber }
|
||||
let checksum = octalToDecimal(octalChecksum)
|
||||
|
||||
var headerDataForChecksum = data.subdata(in: blockStartIndex..<blockStartIndex + 512).toArray(type: UInt8.self)
|
||||
for i in 148..<156 {
|
||||
headerDataForChecksum[i] = 0x20
|
||||
}
|
||||
|
||||
// Some implementations treat bytes as signed integers, but some don't.
|
||||
// So we check both case, coincedence in one of them will pass the checksum test.
|
||||
let unsignedOurChecksumArray = headerDataForChecksum.map { UInt($0) }
|
||||
let signedOurChecksumArray = headerDataForChecksum.map { Int($0) }
|
||||
|
||||
let unsignedOurChecksum = unsignedOurChecksumArray.reduce(0) { $0 + $1 }
|
||||
let signedOurChecksum = signedOurChecksumArray.reduce(0) { $0 + $1 }
|
||||
guard unsignedOurChecksum == UInt(checksum) || signedOurChecksum == checksum
|
||||
else { throw TarError.WrongHeaderChecksum }
|
||||
|
||||
index += 8
|
||||
|
||||
// File type
|
||||
let fileType = String(bytes: [data[index]], encoding: .ascii)
|
||||
index += 1
|
||||
|
||||
// Linked file name
|
||||
let linkedFileName = data.nullEndedAsciiString(index, 100)
|
||||
index += 100
|
||||
|
||||
if posixIndicator == "ustar\u{00}" || posixIndicator == "ustar\u{20}" {
|
||||
index += 6
|
||||
|
||||
let ustarVersion = String(data: data.subdata(in: index..<index + 2), encoding: .ascii)
|
||||
guard ustarVersion == "00" else { throw TarError.WrongUstarVersion }
|
||||
index += 2
|
||||
|
||||
let ownerUserName = data.nullEndedAsciiString(index, 32)
|
||||
index += 32
|
||||
|
||||
let ownerGroupName = data.nullEndedAsciiString(index, 32)
|
||||
index += 32
|
||||
|
||||
let deviceMajorNumber = data.nullSpaceEndedAsciiString(index, 8)
|
||||
index += 8
|
||||
|
||||
let deviceMinorNumber = data.nullSpaceEndedAsciiString(index, 8)
|
||||
index += 8
|
||||
|
||||
let fileNamePrefix = data.nullEndedAsciiString(index, 155)
|
||||
index += 155
|
||||
}
|
||||
|
||||
// File data
|
||||
index = blockStartIndex + 512
|
||||
output.append(data.subdata(in: index..<index + fileSize))
|
||||
|
||||
index += fileSize
|
||||
index = roundTo512(value: index)
|
||||
|
||||
blockStartIndex = index
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// static func parsePrePosix(_ data: Data) throws -> [Data] {
|
||||
// return []
|
||||
// }
|
||||
|
||||
// static func parseUstar(_ data: Data) throws -> [Data] {
|
||||
// return []
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
fileprivate extension Data {
|
||||
|
||||
fileprivate func nullEndedBuffer(_ startIndex: Int, _ cutoff: Int) -> [UInt8] {
|
||||
var index = startIndex
|
||||
var buffer = [UInt8]()
|
||||
while true {
|
||||
if self[index] == 0 || index - startIndex >= cutoff {
|
||||
break
|
||||
}
|
||||
buffer.append(self[index])
|
||||
index += 1
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
fileprivate func nullEndedAsciiString(_ startIndex: Int, _ cutoff: Int) -> String? {
|
||||
return String(bytes: self.nullEndedBuffer(startIndex, cutoff), encoding: .ascii)
|
||||
}
|
||||
|
||||
fileprivate func nullSpaceEndedBuffer(_ startIndex: Int, _ cutoff: Int) -> [UInt8] {
|
||||
var index = startIndex
|
||||
var buffer = [UInt8]()
|
||||
while true {
|
||||
if self[index] == 0 || self[index] == 0x20 || index - startIndex >= cutoff {
|
||||
break
|
||||
}
|
||||
buffer.append(self[index])
|
||||
index += 1
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
fileprivate func nullSpaceEndedAsciiString(_ startIndex: Int, _ cutoff: Int) -> String? {
|
||||
return String(bytes: self.nullSpaceEndedBuffer(startIndex, cutoff), encoding: .ascii)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileprivate func octalToDecimal(_ number: Int) -> Int {
|
||||
var octal = number
|
||||
var decimal = 0, i = 0
|
||||
while octal != 0 {
|
||||
let remainder = octal % 10
|
||||
octal /= 10
|
||||
decimal += remainder * Int(pow(8, Double(i)))
|
||||
i += 1
|
||||
}
|
||||
return decimal
|
||||
}
|
||||
|
||||
fileprivate func roundTo512(value: Int) -> Int {
|
||||
let fractionNum = Double(value) / 512
|
||||
let roundedNum = Int(ceil(fractionNum))
|
||||
return roundedNum * 512
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// TarTests.swift
|
||||
// SWCompression
|
||||
//
|
||||
// Created by Timofey Solomko on 05.05.17.
|
||||
// Copyright © 2017 Timofey Solomko. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import SWCompression
|
||||
|
||||
class TarTests: XCTestCase {
|
||||
|
||||
static let testType: String = "tar"
|
||||
|
||||
func test() {
|
||||
guard let testData = try? Data(contentsOf: Constants.url(forTest: "test", withType: TarTests.testType),
|
||||
options: .mappedIfSafe) else {
|
||||
XCTFail("Failed to load test archive")
|
||||
return
|
||||
}
|
||||
|
||||
_ = try? TarContainer.files(from: testData)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5a8af8043bfdecf913c25ac11e65acd592b94281010867a92b99d15a6ce4ffb4
|
||||
size 2560
|
||||
Reference in New Issue
Block a user