From f9b1f8274dd3bb943d4a13f81c30102dc794e87a Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 14 Oct 2020 23:34:45 +0300 Subject: [PATCH 01/15] Fix localization-related xcode's complaints --- SWCompression.xcodeproj/project.pbxproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 83eb2cfa..b884cce6 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -943,10 +943,11 @@ }; buildConfigurationList = 06BE1AC21DB410F100EE0F59 /* Build configuration list for PBXProject "SWCompression" */; compatibilityVersion = "Xcode 9.3"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 06BE1ABE1DB410F100EE0F59; productRefGroup = 06BE1AC91DB410F100EE0F59 /* Products */; From ab75e0944f2a40a06fc20104099205b9d36ad990 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 14 Oct 2020 23:45:46 +0300 Subject: [PATCH 02/15] [BZip2] Fix compilation warning --- Sources/BZip2/BZip2.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index d1c9e8fb..ad717fbe 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -69,7 +69,7 @@ public class BZip2: DecompressionAlgorithm { guard isRandomized == 0 else { throw BZip2Error.randomizedBlock } - var pointer = bitReader.int(fromBits: 24) + let pointer = bitReader.int(fromBits: 24) func computeUsed() -> [Bool] { let huffmanUsedMap = bitReader.int(fromBits: 16) From 47655ccf706c7f0bb016df476336ae82bfdb7dba Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 14 Oct 2020 23:49:54 +0300 Subject: [PATCH 03/15] [Tests] Add a test for a not-a-zip file --- Tests/ZipTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/ZipTests.swift b/Tests/ZipTests.swift index baf3a3db..4abeddb5 100644 --- a/Tests/ZipTests.swift +++ b/Tests/ZipTests.swift @@ -10,6 +10,11 @@ class ZipTests: XCTestCase { private static let testType: String = "zip" + func testBadFile_invalid() throws { + let testData = try Constants.data(forAnswer: "test6") + XCTAssertThrowsError(try ZipContainer.open(container: testData)) + } + func testBigContainer() throws { let testData = try Constants.data(forTest: "SWCompressionSourceCode", withType: ZipTests.testType) From fa552e9b61150a93a36c7234e8101abfcb617c69 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 14 Oct 2020 23:51:27 +0300 Subject: [PATCH 04/15] [ZIP] Fix a crash when opening a file without EndOfCD Now ZipError.notFoundCentralDirectoryEnd is correctly thrown as was originally intended. --- Sources/ZIP/ZipContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ZIP/ZipContainer.swift b/Sources/ZIP/ZipContainer.swift index f02dbe08..9027a40e 100644 --- a/Sources/ZIP/ZipContainer.swift +++ b/Sources/ZIP/ZipContainer.swift @@ -152,7 +152,7 @@ public class ZipContainer: Container { // We found it! break } - if byteReader.offset == 0 { + if byteReader.offset == 4 { throw ZipError.notFoundCentralDirectoryEnd } byteReader.offset -= 5 From 0282bcf15109c67c0304c032cb5f9748d6427ff7 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 14 Oct 2020 23:51:47 +0300 Subject: [PATCH 05/15] [Tests] Add a test for opening very short file as a zip --- Tests/ZipTests.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/ZipTests.swift b/Tests/ZipTests.swift index 4abeddb5..69fd3ec6 100644 --- a/Tests/ZipTests.swift +++ b/Tests/ZipTests.swift @@ -10,6 +10,10 @@ class ZipTests: XCTestCase { private static let testType: String = "zip" + func testBadFile_short() { + XCTAssertThrowsError(try ZipContainer.open(container: Data([0, 1, 2, 3, 4, 5, 6, 7]))) + } + func testBadFile_invalid() throws { let testData = try Constants.data(forAnswer: "test6") XCTAssertThrowsError(try ZipContainer.open(container: testData)) From 9360a8a9b80d71f21ad1a9a3e0d09a6192a2ec9e Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Wed, 14 Oct 2020 23:53:07 +0300 Subject: [PATCH 06/15] [ZIP] Fix a crash when opening a very small file Such files (smaller than 22 bytes) definitely don't have a EndOfCD and in this case ZipError.notFoundCentralDirectoryEnd should be properly thrown. --- Sources/ZIP/ZipContainer.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/ZIP/ZipContainer.swift b/Sources/ZIP/ZipContainer.swift index 9027a40e..6b638a2e 100644 --- a/Sources/ZIP/ZipContainer.swift +++ b/Sources/ZIP/ZipContainer.swift @@ -141,6 +141,10 @@ public class ZipContainer: Container { } private static func infoWithHelper(_ data: Data) throws -> [ZipEntryInfoHelper] { + // Valid ZIP container must contain at least an End of Central Directory record, which is at least 22 bytes long. + guard data.count >= 22 + else { throw ZipError.notFoundCentralDirectoryEnd } + let byteReader = ByteReader(data: data) var entries = [ZipEntryInfoHelper]() From 28fcfed63202ad4cb51a803304624fb0e40fbfed Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 15 Oct 2020 10:00:45 +0300 Subject: [PATCH 07/15] [Tests] Add 7z tests for a very short and invalid files --- Tests/SevenZipTests.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/SevenZipTests.swift b/Tests/SevenZipTests.swift index 049a4fbe..affbe2c2 100644 --- a/Tests/SevenZipTests.swift +++ b/Tests/SevenZipTests.swift @@ -10,6 +10,15 @@ class SevenZipTests: XCTestCase { private static let testType: String = "7z" + func testBadFile_short() { + XCTAssertThrowsError(try SevenZipContainer.open(container: Data([0, 1, 2]))) + } + + func testBadFile_invalid() throws { + let testData = try Constants.data(forAnswer: "test6") + XCTAssertThrowsError(try SevenZipContainer.open(container: testData)) + } + func test1() throws { let testData = try Constants.data(forTest: "test1", withType: SevenZipTests.testType) let entries = try SevenZipContainer.open(container: testData) From 3670bba2d18b860d5cf615e1e986a44945fd1929 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 15 Oct 2020 10:02:34 +0300 Subject: [PATCH 08/15] [7-Zip] Fix a crash when opening a very small file Such files (smaller than 32 bytes) definitely don't have a SignatureHeader and in this case SevenZipError.wrongSignature should be properly thrown. --- Sources/7-Zip/7zContainer.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Sources/7-Zip/7zContainer.swift b/Sources/7-Zip/7zContainer.swift index d18e3018..0033022b 100644 --- a/Sources/7-Zip/7zContainer.swift +++ b/Sources/7-Zip/7zContainer.swift @@ -202,6 +202,10 @@ public class SevenZipContainer: Container { } private static func readHeader(_ data: Data) throws -> SevenZipHeader? { + // Valid 7-Zip container must contain at least a SignatureHeader, which is 32 bytes long. + guard data.count >= 32 + else { throw SevenZipError.wrongSignature } + let bitReader = MsbBitReader(data: data) // **SignatureHeader** @@ -223,19 +227,18 @@ public class SevenZipContainer: Container { let nextHeaderSize = bitReader.int(fromBytes: 8) let nextHeaderCRC = bitReader.uint32() - bitReader.offset = 12 + bitReader.offset -= 20 guard CheckSums.crc32(bitReader.bytes(count: 20)) == startHeaderCRC else { throw SevenZipError.wrongCRC } // **Header** bitReader.offset += nextHeaderOffset - let headerStartIndex = bitReader.offset - let headerEndIndex: Int - if bitReader.isFinished { return nil // In case of completely empty container. } + let headerStartIndex = bitReader.offset + let headerEndIndex: Int let type = bitReader.byte() let header: SevenZipHeader From 5d357702e1f5770369afabbbe8d3135a0b63c079 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 15 Oct 2020 15:09:48 +0300 Subject: [PATCH 09/15] [Tests] Add an empty lzma test --- SWCompression.xcodeproj/project.pbxproj | 4 ++++ Tests/LzmaTests.swift | 5 +++++ Tests/Test Files | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index b884cce6..9d1bbf67 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -213,6 +213,7 @@ 06F066771FFB763400312A82 /* test8.bz2 in Resources */ = {isa = PBXBuildFile; fileRef = 06F066111FFB763300312A82 /* test8.bz2 */; }; 06F276DF1F2BAB4A00E67335 /* 7zEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F276DE1F2BAB4900E67335 /* 7zEntry.swift */; }; 06FEAD921F54B9CD00AD016E /* EncodingTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FEAD911F54B9CD00AD016E /* EncodingTree.swift */; }; + E66F36242538726E00076A6E /* test_empty.lzma in Resources */ = {isa = PBXBuildFile; fileRef = E66F36232538726E00076A6E /* test_empty.lzma */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -433,6 +434,7 @@ 06F276DE1F2BAB4900E67335 /* 7zEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 7zEntry.swift; sourceTree = ""; }; 06FEAD911F54B9CD00AD016E /* EncodingTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EncodingTree.swift; path = Sources/Common/CodingTree/EncodingTree.swift; sourceTree = SOURCE_ROOT; }; 06FED40B1DD7717E0013DFB2 /* BZip2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BZip2.swift; sourceTree = ""; }; + E66F36232538726E00076A6E /* test_empty.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_empty.lzma; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -838,6 +840,7 @@ 06F065F41FFB763300312A82 /* test9.lzma */, 06F065F31FFB763300312A82 /* test10.lzma */, 06F065F21FFB763300312A82 /* test11.lzma */, + E66F36232538726E00076A6E /* test_empty.lzma */, ); path = LZMA; sourceTree = ""; @@ -1032,6 +1035,7 @@ 06F066391FFB763400312A82 /* test8.xz in Resources */, 06F0662F1FFB763400312A82 /* test9.gz in Resources */, 064DF453245618A300D285F3 /* bad_cd_ext_ts.zip in Resources */, + E66F36242538726E00076A6E /* test_empty.lzma in Resources */, 06F066751FFB763400312A82 /* test1.bz2 in Resources */, 064D01A920FD071300CAE058 /* текстовый файл.answer in Resources */, 06F0662D1FFB763400312A82 /* test6.gz in Resources */, diff --git a/Tests/LzmaTests.swift b/Tests/LzmaTests.swift index 63a0c68d..a6f43613 100644 --- a/Tests/LzmaTests.swift +++ b/Tests/LzmaTests.swift @@ -34,4 +34,9 @@ class LzmaTests: XCTestCase { try self.perform(test: "test11") } + func testLzmaEmpty() throws { + let testData = try Constants.data(forTest: "test_empty", withType: LzmaTests.testType) + XCTAssertEqual(try LZMA.decompress(data: testData), Data()) + } + } diff --git a/Tests/Test Files b/Tests/Test Files index b2f6a804..4222ee11 160000 --- a/Tests/Test Files +++ b/Tests/Test Files @@ -1 +1 @@ -Subproject commit b2f6a8042c372fab86e185e3d7000436dea6d769 +Subproject commit 4222ee113e36a3e54dba53dd01b97f8a20b93bb6 From 93fc1d3da32e284a8c23b1955fa5633e5b5e3b71 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 15 Oct 2020 23:26:49 +0300 Subject: [PATCH 10/15] [Tests] Add an empty zlib test --- SWCompression.xcodeproj/project.pbxproj | 4 ++++ Tests/Test Files | 2 +- Tests/ZlibTests.swift | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 9d1bbf67..df4ad98c 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -214,6 +214,7 @@ 06F276DF1F2BAB4A00E67335 /* 7zEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F276DE1F2BAB4900E67335 /* 7zEntry.swift */; }; 06FEAD921F54B9CD00AD016E /* EncodingTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FEAD911F54B9CD00AD016E /* EncodingTree.swift */; }; E66F36242538726E00076A6E /* test_empty.lzma in Resources */ = {isa = PBXBuildFile; fileRef = E66F36232538726E00076A6E /* test_empty.lzma */; }; + E66F362C2538E2B700076A6E /* test_empty.zlib in Resources */ = {isa = PBXBuildFile; fileRef = E66F362B2538E2B700076A6E /* test_empty.zlib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -435,6 +436,7 @@ 06FEAD911F54B9CD00AD016E /* EncodingTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EncodingTree.swift; path = Sources/Common/CodingTree/EncodingTree.swift; sourceTree = SOURCE_ROOT; }; 06FED40B1DD7717E0013DFB2 /* BZip2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BZip2.swift; sourceTree = ""; }; E66F36232538726E00076A6E /* test_empty.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_empty.lzma; sourceTree = ""; }; + E66F362B2538E2B700076A6E /* test_empty.zlib */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_empty.zlib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -747,6 +749,7 @@ children = ( 06F065C41FFB763300312A82 /* test.zlib */, 06F065C51FFB763300312A82 /* random_file.zlib */, + E66F362B2538E2B700076A6E /* test_empty.zlib */, ); path = Zlib; sourceTree = ""; @@ -1039,6 +1042,7 @@ 06F066751FFB763400312A82 /* test1.bz2 in Resources */, 064D01A920FD071300CAE058 /* текстовый файл.answer in Resources */, 06F0662D1FFB763400312A82 /* test6.gz in Resources */, + E66F362C2538E2B700076A6E /* test_empty.zlib in Resources */, 06F0664D1FFB763400312A82 /* test_ustar.tar in Resources */, 06F0665A1FFB763400312A82 /* test11.lzma in Resources */, 06F066401FFB763400312A82 /* test_zip_bzip2.zip in Resources */, diff --git a/Tests/Test Files b/Tests/Test Files index 4222ee11..073da4e0 160000 --- a/Tests/Test Files +++ b/Tests/Test Files @@ -1 +1 @@ -Subproject commit 4222ee113e36a3e54dba53dd01b97f8a20b93bb6 +Subproject commit 073da4e01c87d9c1e599e507d1bb21e307c564ad diff --git a/Tests/ZlibTests.swift b/Tests/ZlibTests.swift index d24dfa56..64c5d3af 100644 --- a/Tests/ZlibTests.swift +++ b/Tests/ZlibTests.swift @@ -37,4 +37,9 @@ class ZlibTests: XCTestCase { XCTAssertEqual(testData, reextractedData) } + func testZlibEmpty() throws { + let testData = try Constants.data(forTest: "test_empty", withType: ZlibTests.testType) + XCTAssertEqual(try ZlibArchive.unarchive(archive: testData), Data()) + } + } From 00af5a9319d155aae798afdbcd3cde897a5b5ae9 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 15 Oct 2020 23:31:23 +0300 Subject: [PATCH 11/15] [BZip2][GZip][LZMA][XZ][Zlib] Fix crashes when opening very small files Instead errors are now thrown. --- Sources/BZip2/BZip2.swift | 4 ++++ Sources/GZip/GzipArchive.swift | 4 ++++ Sources/GZip/GzipHeader.swift | 4 ++++ Sources/LZMA/LZMA.swift | 4 ++++ Sources/LZMA/LZMARangeDecoder.swift | 4 ++++ Sources/XZ/XZArchive.swift | 8 ++++++++ Sources/Zlib/ZlibArchive.swift | 4 ++++ Sources/Zlib/ZlibHeader.swift | 4 ++++ 8 files changed, 36 insertions(+) diff --git a/Sources/BZip2/BZip2.swift b/Sources/BZip2/BZip2.swift index ad717fbe..aed1442f 100644 --- a/Sources/BZip2/BZip2.swift +++ b/Sources/BZip2/BZip2.swift @@ -26,6 +26,10 @@ public class BZip2: DecompressionAlgorithm { } static func decompress(_ bitReader: MsbBitReader) throws -> Data { + // Valid BZip2 "archive" must contain at least 14 bytes of data. + guard bitReader.bitsLeft >= 14 * 8 + else { throw BZip2Error.wrongMagic } + /// An array for storing output data var out = Data() diff --git a/Sources/GZip/GzipArchive.swift b/Sources/GZip/GzipArchive.swift index a5b1cad4..648e1f35 100644 --- a/Sources/GZip/GzipArchive.swift +++ b/Sources/GZip/GzipArchive.swift @@ -77,6 +77,10 @@ public class GzipArchive: Archive { } private static func processMember(_ bitReader: LsbBitReader) throws -> Member { + // Valid GZip archive must contain at least 33 bytes of data. + guard bitReader.bitsLeft >= 33 * 8 + else { throw GzipError.wrongMagic } + let header = try GzipHeader(bitReader) let memberData = try Deflate.decompress(bitReader) diff --git a/Sources/GZip/GzipHeader.swift b/Sources/GZip/GzipHeader.swift index 1ed22441..afce8f26 100644 --- a/Sources/GZip/GzipHeader.swift +++ b/Sources/GZip/GzipHeader.swift @@ -58,6 +58,10 @@ public struct GzipHeader { } init(_ byteReader: ByteReader) throws { + // Valid GZip header must contain at least 2 bytes of data. + guard byteReader.bytesLeft >= 10 + else { throw GzipError.wrongMagic } + // First two bytes should be correct 'magic' bytes let magic = byteReader.uint16() guard magic == 0x8b1f else { throw GzipError.wrongMagic } diff --git a/Sources/LZMA/LZMA.swift b/Sources/LZMA/LZMA.swift index fd98db46..09d5f5cd 100644 --- a/Sources/LZMA/LZMA.swift +++ b/Sources/LZMA/LZMA.swift @@ -23,6 +23,10 @@ public class LZMA: DecompressionAlgorithm { - Returns: Decompressed data. */ public static func decompress(data: Data) throws -> Data { + // Valid LZMA "archive" must contain at least 13 bytes of data with properties and uncompressed size. + guard data.count >= 13 + else { throw LZMAError.wrongProperties } + let byteReader = ByteReader(data: data) let properties = try LZMAProperties(byteReader) let uncompSize = byteReader.int(fromBytes: 8) diff --git a/Sources/LZMA/LZMARangeDecoder.swift b/Sources/LZMA/LZMARangeDecoder.swift index 7f616016..aa2b4778 100644 --- a/Sources/LZMA/LZMARangeDecoder.swift +++ b/Sources/LZMA/LZMARangeDecoder.swift @@ -19,6 +19,10 @@ final class LZMARangeDecoder { } init?(_ byteReader: ByteReader) { + // To initialize rande decoder at least 5 bytes are necessary. + guard byteReader.bytesLeft >= 5 + else { return nil } + self.byteReader = byteReader let byte = self.byteReader.byte() diff --git a/Sources/XZ/XZArchive.swift b/Sources/XZ/XZArchive.swift index 9ddb74da..d53a544a 100644 --- a/Sources/XZ/XZArchive.swift +++ b/Sources/XZ/XZArchive.swift @@ -35,6 +35,10 @@ public class XZArchive: Archive { var result = Data() while !byteReader.isFinished { + // Valid XZ archive must contain at least 32 bytes of data. + guard byteReader.bytesLeft >= 32 + else { throw XZError.wrongMagic } + let streamResult = try processStream(byteReader) result.append(streamResult.data) guard !streamResult.checkError @@ -68,6 +72,10 @@ public class XZArchive: Archive { var result = [Data]() while !byteReader.isFinished { + // Valid XZ archive must contain at least 32 bytes of data. + guard byteReader.bytesLeft >= 32 + else { throw XZError.wrongMagic } + let streamResult = try processStream(byteReader) result.append(streamResult.data) guard !streamResult.checkError diff --git a/Sources/Zlib/ZlibArchive.swift b/Sources/Zlib/ZlibArchive.swift index 8e6b8593..e7d5084a 100644 --- a/Sources/Zlib/ZlibArchive.swift +++ b/Sources/Zlib/ZlibArchive.swift @@ -23,6 +23,10 @@ public class ZlibArchive: Archive { - Returns: Unarchived data. */ public static func unarchive(archive data: Data) throws -> Data { + // Valid Zlib archive must contain at least 8 bytes of data. + guard data.count >= 8 + else { throw ZlibError.wrongCompressionMethod } + /// Object with input data which supports convenient work with bit shifts. let bitReader = LsbBitReader(data: data) diff --git a/Sources/Zlib/ZlibHeader.swift b/Sources/Zlib/ZlibHeader.swift index a51b659f..36bbaedc 100644 --- a/Sources/Zlib/ZlibHeader.swift +++ b/Sources/Zlib/ZlibHeader.swift @@ -45,6 +45,10 @@ public struct ZlibHeader { } init(_ byteReader: ByteReader) throws { + // Valid Zlib header must contain at least 2 bytes of data. + guard byteReader.bytesLeft >= 2 + else { throw ZlibError.wrongCompressionMethod } + // compressionMethod and compressionInfo combined are needed later for integrity check. let cmf = byteReader.byte() // First four bits are compression method. From d88127d650f7107431ba78a8b340ab19d36d0ba3 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 15 Oct 2020 23:33:37 +0300 Subject: [PATCH 12/15] [Tests] Add tests for a very small and invalid files for BZip2, GZip, LZMA, TAR, XZ, and Zlib --- Tests/BZip2Tests.swift | 9 +++++++++ Tests/GzipTests.swift | 12 ++++++++++++ Tests/LzmaTests.swift | 13 +++++++++++++ Tests/TarTests.swift | 11 +++++++++++ Tests/XzTests.swift | 10 ++++++++++ Tests/ZlibTests.swift | 10 ++++++++++ 6 files changed, 65 insertions(+) diff --git a/Tests/BZip2Tests.swift b/Tests/BZip2Tests.swift index 4db58c1d..3107c973 100644 --- a/Tests/BZip2Tests.swift +++ b/Tests/BZip2Tests.swift @@ -54,4 +54,13 @@ class BZip2Tests: XCTestCase { try self.perform(test: "test9") } + func testBadFile_short() { + XCTAssertThrowsError(try BZip2.decompress(data: Data([0]))) + } + + func testBadFile_invalid() throws { + let testData = try Constants.data(forAnswer: "test6") + XCTAssertThrowsError(try BZip2.decompress(data: testData)) + } + } diff --git a/Tests/GzipTests.swift b/Tests/GzipTests.swift index 13fc74e0..8f662df5 100644 --- a/Tests/GzipTests.swift +++ b/Tests/GzipTests.swift @@ -139,4 +139,16 @@ class GzipTests: XCTestCase { XCTAssertEqual(data, answerData) } + func testBadFile_short() { + XCTAssertThrowsError(try GzipArchive.unarchive(archive: Data([0]))) + XCTAssertThrowsError(try GzipArchive.multiUnarchive(archive: Data([0]))) + XCTAssertThrowsError(try GzipHeader(archive: Data([0]))) + } + + func testBadFile_invalid() throws { + let testData = try Constants.data(forAnswer: "test6") + XCTAssertThrowsError(try GzipArchive.unarchive(archive: testData)) + XCTAssertThrowsError(try GzipArchive.multiUnarchive(archive: testData)) + } + } diff --git a/Tests/LzmaTests.swift b/Tests/LzmaTests.swift index a6f43613..1081a273 100644 --- a/Tests/LzmaTests.swift +++ b/Tests/LzmaTests.swift @@ -39,4 +39,17 @@ class LzmaTests: XCTestCase { XCTAssertEqual(try LZMA.decompress(data: testData), Data()) } + func testBadFile_short() { + // Not enough data for LZMA properties. + XCTAssertThrowsError(try LZMA.decompress(data: Data([0, 1, 2, 3]))) + // Not enough data to initialize range decoder. + XCTAssertThrowsError(try LZMA.decompress(data: Data([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]))) + } + + func testBadFile_invalid() throws { + let testData = try Constants.data(forAnswer: "test7") + XCTAssertThrowsError(try LZMA.decompress(data: testData)) + } + + } diff --git a/Tests/TarTests.swift b/Tests/TarTests.swift index 2debc652..17ccce19 100644 --- a/Tests/TarTests.swift +++ b/Tests/TarTests.swift @@ -10,6 +10,17 @@ class TarTests: XCTestCase { private static let testType: String = "tar" + func testBadFile_short() { + XCTAssertThrowsError(try TarContainer.open(container: Data([0, 1, 2]))) + } + + func testBadFile_invalid() throws { + // This is potentially a misleading test, since there is no way to guarantee that a file is not a TAR container. + // We use randomly generated data, since the 0-filled data is processed as an empty container. + let testData = try Constants.data(forAnswer: "test7") + XCTAssertThrowsError(try TarContainer.open(container: testData)) + } + func test() throws { let testData = try Constants.data(forTest: "test", withType: TarTests.testType) diff --git a/Tests/XzTests.swift b/Tests/XzTests.swift index 558c7bf3..af7a1de9 100644 --- a/Tests/XzTests.swift +++ b/Tests/XzTests.swift @@ -110,4 +110,14 @@ class XZTests: XCTestCase { XCTAssertEqual(decompressedData, answerData) } + func testBadFile_short() { + XCTAssertThrowsError(try XZArchive.unarchive(archive: Data([0, 1, 2]))) + XCTAssertThrowsError(try XZArchive.splitUnarchive(archive: Data([0, 1, 2]))) + } + + func testBadFile_invalid() throws { + let testData = try Constants.data(forAnswer: "test6") + XCTAssertThrowsError(try XZArchive.unarchive(archive: testData)) + } + } diff --git a/Tests/ZlibTests.swift b/Tests/ZlibTests.swift index 64c5d3af..5040f746 100644 --- a/Tests/ZlibTests.swift +++ b/Tests/ZlibTests.swift @@ -42,4 +42,14 @@ class ZlibTests: XCTestCase { XCTAssertEqual(try ZlibArchive.unarchive(archive: testData), Data()) } + func testBadFile_short() { + XCTAssertThrowsError(try ZlibArchive.unarchive(archive: Data([0x78]))) + XCTAssertThrowsError(try ZlibHeader(archive: Data([0x78]))) + } + + func testBadFile_invalid() throws { + let testData = try Constants.data(forAnswer: "test6") + XCTAssertThrowsError(try ZlibArchive.unarchive(archive: testData)) + } + } From a07fc290a06a7db2474b858ca7f4a8fa124b316f Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 15 Oct 2020 23:33:52 +0300 Subject: [PATCH 13/15] Small fixes to code comments --- Sources/LZMA/LZMADecoder.swift | 3 +-- Sources/TAR/TarEntryInfo.swift | 4 ++-- Sources/Zlib/ZlibHeader.swift | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Sources/LZMA/LZMADecoder.swift b/Sources/LZMA/LZMADecoder.swift index a18665c1..3facb609 100644 --- a/Sources/LZMA/LZMADecoder.swift +++ b/Sources/LZMA/LZMADecoder.swift @@ -255,8 +255,7 @@ final class LZMADecoder { // Middle bits of distance are decoded as direct bits from RangeDecoder. dist += rangeDecoder.decode(directBits: (numDirectBits - LZMAConstants.numAlignBits)) << LZMAConstants.numAlignBits - // Low 4 bits are decoded with a bit tree decoder (called 'AlignDecoder')... - // ...with "Reverse" scheme. + // Low 4 bits are decoded with a bit tree decoder (called 'AlignDecoder') using "Reverse" scheme. dist += alignDecoder.reverseDecode(with: rangeDecoder) } rep0 = dist diff --git a/Sources/TAR/TarEntryInfo.swift b/Sources/TAR/TarEntryInfo.swift index 583bd276..535e042c 100644 --- a/Sources/TAR/TarEntryInfo.swift +++ b/Sources/TAR/TarEntryInfo.swift @@ -264,7 +264,7 @@ public struct TarEntryInfo: ContainerEntryInfo { let linkName = byteReader.tarCString(maxLength: 100) // There are two different formats utilizing this section of TAR header: GNU format and POSIX (aka "ustar"; - // also PAX containers can also be considered POSIX). They differ in the value of magic field as well as what + // PAX containers can also be considered as POSIX). They differ in the value of magic field as well as what // comes after deviceMinorNumber field. While "ustar" format may contain prefix for file name, GNU format // uses this place for storing atime/ctime and fields related to sparse-files. In practice, these fields are // rarely used by GNU tar and only present if "incremental backups" options were used. Thus, GNU format TAR @@ -309,7 +309,7 @@ public struct TarEntryInfo: ContainerEntryInfo { if local != nil || global != nil { self.format = .pax - } else if magic == 0x00_20_20_72_61_74_73_75 || longName != nil || longLinkName != nil { + } else if magic == 0x0020207261747375 || longName != nil || longLinkName != nil { self.format = .gnu } else if magic == 0x3030007261747375 || magic == 0x3030207261747375 { self.format = .ustar diff --git a/Sources/Zlib/ZlibHeader.swift b/Sources/Zlib/ZlibHeader.swift index 36bbaedc..54f3f87f 100644 --- a/Sources/Zlib/ZlibHeader.swift +++ b/Sources/Zlib/ZlibHeader.swift @@ -69,7 +69,7 @@ public struct ZlibHeader { // fcheck, fdict and compresionLevel together make flags byte which is used in integrity check. let flags = byteReader.byte() - // First five bits are fcheck bits which are supposed to be integrity check: + // First five bits are fcheck bits which are used for integrity check: // let fcheck = flags & 0x1F // Sixth bit indicate if archive contain Adler-32 checksum of preset dictionary. From 8da109fe213952b26c8785eb3c759116607d591e Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Thu, 15 Oct 2020 23:55:56 +0300 Subject: [PATCH 14/15] CI: fix tests not running properly on macos with Swift 5.3 --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index dc9596a5..70ec0b36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -66,6 +66,12 @@ jobs: - echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig - export XCODE_XCCONFIG_FILE="$xcconfig" - carthage bootstrap + - git submodule update --init --recursive + - cp -f Tests/Test Files/gitattributes-copy Tests/Test Files/.gitattributes + - cd Tests/Test Files/ + - git lfs pull + - git lfs checkout + - cd - script: - ./utils.py ci script-macos - stage: test From fc38cc19df4b52377b5ff138891b6a7493e74b80 Mon Sep 17 00:00:00 2001 From: Timofey Solomko Date: Fri, 16 Oct 2020 15:49:05 +0300 Subject: [PATCH 15/15] Prepare for 4.5.7 release --- .jazzy.yaml | 4 ++-- CHANGELOG.md | 11 +++++++++++ SWCompression.podspec | 2 +- SWCompression.xcodeproj/SWCompression.plist | 4 ++-- SWCompression.xcodeproj/TestSWCompression.plist | 4 ++-- SWCompression.xcodeproj/project.pbxproj | 8 ++++---- Sources/swcomp/main.swift | 2 +- 7 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.jazzy.yaml b/.jazzy.yaml index 0b6549cb..6fa59613 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -3,11 +3,11 @@ sourcekitten_sourcefile: docs.json clean: true author: Timofey Solomko module: SWCompression -module_version: 4.5.6 +module_version: 4.5.7 copyright: '© 2020 Timofey Solomko' readme: README.md github_url: https://github.com/tsolomko/SWCompression -github_file_prefix: https://github.com/tsolomko/SWCompression/tree/4.5.6 +github_file_prefix: https://github.com/tsolomko/SWCompression/tree/4.5.7 theme: fullwidth custom_categories: diff --git a/CHANGELOG.md b/CHANGELOG.md index fae75d4a..269a0fd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 4.5.7 + +- Fixed a crash when trying to open a very small file as a 7-Zip container, a BZip2 "archive", a GZip archive (both when + using `unarchive` and `multiUnarchive`), a LZMA "archive", a XZ archive (both when using `unarchive` and `splitUnarchive`), + a ZIP container, or a Zlib archive. +- Fixed a crash when trying to open an unexpectedly truncated file as a GZip or XZ archive. +- Fixed a crash when trying to open an invalid file as a ZIP container. The `ZipError.notFoundCentralDirectoryEnd` error + is now thrown instead as was originally intended. +- Fixed a crash when trying to read a GZip or Zlib header from a very small file. +- Fixed a compilation warning in BZip2. + ## 4.5.6 - Fixed a compiling issue on Linux with Swift 5.3. diff --git a/SWCompression.podspec b/SWCompression.podspec index 35489e51..7333c861 100644 --- a/SWCompression.podspec +++ b/SWCompression.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SWCompression" - s.version = "4.5.6" + s.version = "4.5.7" s.summary = "A framework with functions for working with compression, archives and containers." s.description = "A framework with (de)compression algorithms and functions for processing various archives and containers." diff --git a/SWCompression.xcodeproj/SWCompression.plist b/SWCompression.xcodeproj/SWCompression.plist index 518b2856..46a41309 100644 --- a/SWCompression.xcodeproj/SWCompression.plist +++ b/SWCompression.xcodeproj/SWCompression.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 4.5.6 + 4.5.7 CFBundleVersion - 73 + 74 NSHumanReadableCopyright Copyright © 2020 Timofey Solomko. All rights reserved. diff --git a/SWCompression.xcodeproj/TestSWCompression.plist b/SWCompression.xcodeproj/TestSWCompression.plist index 46874253..a8ecec80 100644 --- a/SWCompression.xcodeproj/TestSWCompression.plist +++ b/SWCompression.xcodeproj/TestSWCompression.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 4.5.6 + 4.5.7 CFBundleVersion - 73 + 74 diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index df4ad98c..bd38906a 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -1249,7 +1249,7 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CURRENT_PROJECT_VERSION = 73; + CURRENT_PROJECT_VERSION = 74; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -1308,7 +1308,7 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CURRENT_PROJECT_VERSION = 73; + CURRENT_PROJECT_VERSION = 74; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; "FRAMEWORK_SEARCH_PATHS[sdk=appletvos*]" = "\"$(SRCROOT)/Carthage/Build/tvOS\""; @@ -1348,7 +1348,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 73; + DYLIB_CURRENT_VERSION = 74; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1375,7 +1375,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 73; + DYLIB_CURRENT_VERSION = 74; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/Sources/swcomp/main.swift b/Sources/swcomp/main.swift index ee44f834..e6bcf370 100644 --- a/Sources/swcomp/main.swift +++ b/Sources/swcomp/main.swift @@ -7,7 +7,7 @@ import Foundation import SWCompression import SwiftCLI -let cli = CLI(name: "swcomp", version: "4.5.6", +let cli = CLI(name: "swcomp", version: "4.5.7", description: """ swcomp - small command-line client for SWCompression framework. Serves as an example of SWCompression usage.