diff --git a/Package.swift b/Package.swift index 3e7ef3e..4343ddb 100644 --- a/Package.swift +++ b/Package.swift @@ -86,7 +86,7 @@ let package = Package( name: "ContainerRegistryTests", dependencies: [.target(name: "ContainerRegistry")], resources: [.process("Resources")] - ), + ), .testTarget(name: "containertoolTests", dependencies: [.target(name: "containertool")]), ], swiftLanguageModes: [.v6] ) diff --git a/Sources/containertool/gzip.swift b/Sources/containertool/gzip.swift index 2234b5f..6db9b4d 100644 --- a/Sources/containertool/gzip.swift +++ b/Sources/containertool/gzip.swift @@ -35,10 +35,36 @@ func gzip(_ bytes: [UInt8]) -> [UInt8] { stream.zfree = nil stream.opaque = nil + // Force identical gzip headers to be created on Linux and macOS. + // + // RFC1952 defines operating system codes which can be embedded in the gzip header. + // + // * Initially, zlib generated a default gzip header with the + // OS field set to `Unknown` (255). + // * https://github.com/madler/zlib/commit/0484693e1723bbab791c56f95597bd7dbe867d03 + // changed the default to `Unix` (3). + // * https://github.com/madler/zlib/commit/ce12c5cd00628bf8f680c98123a369974d32df15 + // changed the default to use a value based on the OS detected + // at compile time. After this, zlib on Linux continued to + // use `Unix` (3) whereas macOS started to use `Apple` (19). + // + // According to RFC1952 Section 2.3.1.2. (Compliance), `Unknown` + // 255 should be used by default where the OS on which the file + // was created is not known. + // + // Different versions of zlib might still produce different + // compressed output for the same input, but using the same default + // value removes one one source of differences between platforms. + + let gz_os_unknown = Int32(255) + var header = gz_header() + header.os = gz_os_unknown + let windowBits: Int32 = 15 + 16 let level = Z_DEFAULT_COMPRESSION let memLevel: Int32 = 8 let rc = CNIOExtrasZlib_deflateInit2(&stream, level, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY) + deflateSetHeader(&stream, &header) precondition(rc == Z_OK, "Unexpected return from zlib init: \(rc)") diff --git a/Tests/containertoolTests/ZlibTests.swift b/Tests/containertoolTests/ZlibTests.swift new file mode 100644 index 0000000..c9152ac --- /dev/null +++ b/Tests/containertoolTests/ZlibTests.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftContainerPlugin open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftContainerPlugin project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftContainerPlugin project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +@testable import containertool +import Crypto +import XCTest + +class ZlibTests: XCTestCase, @unchecked Sendable { + // Check that compressing the same data on macOS and Linux produces the same output. + func testGzipHeader() async throws { + let data = "test" + + let result = gzip([UInt8](data.utf8)) + XCTAssertEqual( + "\(SHA256.hash(data: result))", + "SHA256 digest: 7dff8d09129482017247cb373e8138772e852a1a02f097d1440387055d2be69c" + ) + } +}