Added first prototype of TAR parsing. And a test for it.

This commit is contained in:
Timofey Solomko
2017-05-06 20:26:58 +03:00
parent ec072acc5d
commit 82ff0dff04
6 changed files with 271 additions and 1 deletions
+1
View File
@@ -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
+26
View File
@@ -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 */,
);
+1 -1
View File
@@ -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()
+214
View File
@@ -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
}
+26
View File
@@ -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)
}
}
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a8af8043bfdecf913c25ac11e65acd592b94281010867a92b99d15a6ce4ffb4
size 2560