diff --git a/.jazzy.yaml b/.jazzy.yaml index f556b310..05c7932a 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -3,11 +3,11 @@ sourcekitten_sourcefile: docs.json clean: false author: Timofey Solomko module: SWCompression -module_version: 4.8.3 +module_version: 4.8.4 copyright: '© 2022 Timofey Solomko' readme: README.md github_url: https://github.com/tsolomko/SWCompression -github_file_prefix: https://github.com/tsolomko/SWCompression/tree/4.8.3 +github_file_prefix: https://github.com/tsolomko/SWCompression/tree/4.8.4 theme: fullwidth custom_categories: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1245227a..f6f8189a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 4.8.4 + +- Fixed an issue where in some cases BZip2 compression would produce incorrect output. +- `TarReader` methods now always return `nil` after reaching the end of a TAR container. + ## 4.8.3 - There are now minimum deployment targets specified in Swift Package Manager manifest. diff --git a/SWCompression.podspec b/SWCompression.podspec index 351a2daf..b9425120 100644 --- a/SWCompression.podspec +++ b/SWCompression.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SWCompression" - s.version = "4.8.3" + s.version = "4.8.4" 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 327e7b10..f167faf3 100644 --- a/SWCompression.xcodeproj/SWCompression.plist +++ b/SWCompression.xcodeproj/SWCompression.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 4.8.3 + 4.8.4 CFBundleVersion - 88 + 89 NSHumanReadableCopyright Copyright © 2022 Timofey Solomko diff --git a/SWCompression.xcodeproj/TestSWCompression.plist b/SWCompression.xcodeproj/TestSWCompression.plist index f40b8805..0012bd54 100644 --- a/SWCompression.xcodeproj/TestSWCompression.plist +++ b/SWCompression.xcodeproj/TestSWCompression.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 4.8.3 + 4.8.4 CFBundleVersion - 88 + 89 diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index b83aea5c..1e33abfb 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -1147,7 +1147,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1400; + LastUpgradeCheck = 1420; ORGANIZATIONNAME = "Timofey Solomko"; TargetAttributes = { 06BE1AC71DB410F100EE0F59 = { @@ -1528,7 +1528,7 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CURRENT_PROJECT_VERSION = 88; + CURRENT_PROJECT_VERSION = 89; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; EAGER_LINKING = YES; @@ -1613,7 +1613,7 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CURRENT_PROJECT_VERSION = 88; + CURRENT_PROJECT_VERSION = 89; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; EAGER_LINKING = YES; @@ -1678,7 +1678,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 88; + DYLIB_CURRENT_VERSION = 89; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1705,7 +1705,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 88; + DYLIB_CURRENT_VERSION = 89; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme index 62668c55..aea7883f 100644 --- a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme +++ b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme @@ -1,6 +1,6 @@ TarEntry? { let headerData = try getData(size: 512) @@ -94,6 +90,10 @@ public struct TarReader { if try getData(size: 512) == Data(count: 512) { return nil } else { + // In this case we have a zero-filled block immediately followed by a non-zero-filled block which do not + // match the EOF marker signature. In practice, this indicates a malformed TAR container, since a + // zero-filled block is not a valid TAR header (and in fact the end result is an error being thrown in + // TarHeader initializer later down the line). try set(offset: offset) } } else if headerData.count < 512 { @@ -162,12 +162,18 @@ public struct TarReader { private func getData(size: Int) throws -> Data { assert(size >= 0, "TarReader.getData(size:): negative size.") - guard size > 0 - else { return Data() } if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { - guard let chunkData = try handle.read(upToCount: size) - else { throw DataError.truncated } - return chunkData + // The documentation for FileHandle.read(upToCount:) is a bit misleading. This method does "return the data + // obtained by reading length bytes starting at the current file pointer" even if the requested amount is + // larger than the available data. What is not clear is when the method returns nil. Apparently, there are + // (at least) two cases when it happens: + // - the file pointer is at the EOF regardless of the argument value, + // - the argument is zero. + // It is also unclear what happens when the argument is negative (it seems that it reads everything until + // the EOF), but the assertion above takes care of this. In any case, instead of returning nil we return + // empty data since both of these situations logically seem equivalent for our purposes. This also allows us + // to eliminate additional guard-check for the size parameter. + return try handle.read(upToCount: size) ?? Data() } else { // Technically, this can throw NSException, but since it is ObjC exception we cannot handle it in Swift. return handle.readData(ofLength: size) diff --git a/Sources/swcomp/main.swift b/Sources/swcomp/main.swift index e023b90b..3f617ca9 100644 --- a/Sources/swcomp/main.swift +++ b/Sources/swcomp/main.swift @@ -7,7 +7,7 @@ import Foundation import SWCompression import SwiftCLI -let _SWC_VERSION = "4.8.3" +let _SWC_VERSION = "4.8.4" let cli = CLI(name: "swcomp", version: _SWC_VERSION, description: """ diff --git a/Tests/BZip2CompressionTests.swift b/Tests/BZip2CompressionTests.swift index b65fb87b..45218f93 100644 --- a/Tests/BZip2CompressionTests.swift +++ b/Tests/BZip2CompressionTests.swift @@ -84,4 +84,14 @@ class BZip2CompressionTests: XCTestCase { try answerTest("test9") } + func testBurrowsWheelerRoundtrip() throws { + // This test is inspired by the reported issue #38 that uncovered a mistake with a pointer variable in BWT. + // "1"s can be anything (except zero), but it must be the same byte value in all places. + // Two consecutive zeros in the middle seem to be crucial for some reason. + let testData = Data([0, 1, 0, 1, 0, 0, 1, 0, 1]) + let compressedData = BZip2.compress(data: testData) + let redecompressedData = try BZip2.decompress(data: compressedData) + XCTAssertEqual(redecompressedData, testData) + } + } diff --git a/Tests/TarReaderTests.swift b/Tests/TarReaderTests.swift index e16e7107..ddc87d8d 100644 --- a/Tests/TarReaderTests.swift +++ b/Tests/TarReaderTests.swift @@ -44,7 +44,6 @@ class TarReaderTests: XCTestCase { } } XCTAssertEqual(entriesCount, 1) - try testHandle.closeCompat() } @@ -104,6 +103,7 @@ class TarReaderTests: XCTestCase { } } XCTAssertEqual(entriesCount, 1) + XCTAssertNil(try reader.read()) try testHandle.closeCompat() } } @@ -125,6 +125,7 @@ class TarReaderTests: XCTestCase { } } XCTAssertEqual(entriesCount, 6) + XCTAssertNil(try reader.read()) try testHandle.closeCompat() } } @@ -158,6 +159,7 @@ class TarReaderTests: XCTestCase { XCTAssertNil(entry!.info.comment) XCTAssertEqual(entry!.data, "Hello, Windows!".data(using: .utf8)) } + XCTAssertNil(try reader.read()) try testHandle.closeCompat() } @@ -178,6 +180,7 @@ class TarReaderTests: XCTestCase { XCTAssertEqual(entry!.data, Data()) } + XCTAssertNil(try reader.read()) try testHandle.closeCompat() } @@ -197,6 +200,7 @@ class TarReaderTests: XCTestCase { XCTAssertNil(entry!.info.comment) XCTAssertNil(entry!.data) } + XCTAssertNil(try reader.read()) try testHandle.closeCompat() } @@ -217,8 +221,8 @@ class TarReaderTests: XCTestCase { XCTAssertEqual(entry!.info.permissions, Permissions(rawValue: 493)) XCTAssertNil(entry!.info.comment) XCTAssertNil(entry!.data) - } + XCTAssertNil(try reader.read()) try testHandle.closeCompat() } @@ -253,6 +257,7 @@ class TarReaderTests: XCTestCase { XCTAssertEqual(entry!.data, answerData) } + XCTAssertNil(try reader.read()) try testHandle.closeCompat() } @@ -273,6 +278,7 @@ class TarReaderTests: XCTestCase { XCTAssertEqual(entry!.data, answerData) } + XCTAssertNil(try reader.read()) try testHandle.closeCompat() } @@ -297,6 +303,7 @@ class TarReaderTests: XCTestCase { } } XCTAssertEqual(entriesCount, 3) + XCTAssertNil(try reader.read()) try testHandle.closeCompat() } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0b606032..46c424be 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -46,7 +46,7 @@ stages: UTILS_PW_XCF_FLAG: '--xcf' macosSwift57: imageName: 'macOS-12' - DEVELOPER_DIR: '/Applications/Xcode_14.0.1.app' + DEVELOPER_DIR: '/Applications/Xcode_14.2.app' WATCHOS_ACTIONS: 'clean test' WATCHOS_SIMULATOR: 'Apple Watch Series 6 (44mm)' UTILS_PW_XCF_FLAG: '--xcf' @@ -108,7 +108,7 @@ stages: containerImage: 'swift:5.6.3-focal' linuxSwift57: imageName: 'ubuntu-20.04' - containerImage: 'swift:5.7-focal' + containerImage: 'swift:5.7.2-focal' pool: vmImage: $(imageName) container: $[ variables['containerImage'] ] @@ -139,7 +139,7 @@ stages: SWIFT_DEV_PATH: 'C:\Library\Swift-development\bin' windowsSwift57: imageName: 'windows-2019' - SWIFT_VERSION: '5.7' + SWIFT_VERSION: '5.7.2' ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin' SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin' pool: @@ -174,7 +174,7 @@ stages: pool: vmImage: 'macOS-12' variables: - DEVELOPER_DIR: '/Applications/Xcode_14.0.1.app' + DEVELOPER_DIR: '/Applications/Xcode_14.2.app' steps: - script: | set -e -o xtrace