diff --git a/Package.swift b/Package.swift index fd33704c..e601c2fe 100644 --- a/Package.swift +++ b/Package.swift @@ -1,12 +1,35 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription +let defines: [SwiftSetting] = [ + .define("SYSTEM_ICONV", .when(platforms: [.linux])) +] + +#if os(Linux) +let dependencies: [PackageDescription.Package.Dependency] = [ + .package(url: "https://github.com/0xfeedface1993/iconv.git", branch: "main"), + .package(url: "https://github.com/0xfeedface1993/CEnca.git", from: "0.1.3"), +] +let products: [Target.Dependency] = [ + .product(name: "iconv", package: "iconv"), + .product(name: "EncodingWrapper", package: "CEnca"), +] +#else +let dependencies: [PackageDescription.Package.Dependency] = [ + .package(url: "https://github.com/apple/swift-log.git", from: "1.5.2"), +] +let products: [Target.Dependency] = [ + .product(name: "Logging", package: "swift-log"), +] +#endif + let package = Package( name: "Zip", products: [ .library(name: "Zip", targets: ["Zip"]) ], + dependencies: dependencies, targets: [ .target( name: "Minizip", @@ -18,12 +41,14 @@ let package = Package( ]), .target( name: "Zip", - dependencies: ["Minizip"], + dependencies: ["Minizip"] + products, path: "Zip", - exclude: ["minizip", "zlib"]), + exclude: ["minizip", "zlib"], + swiftSettings: defines), .testTarget( name: "ZipTests", dependencies: ["Zip"], - path: "ZipTests"), + path: "ZipTests", + resources: [.process("Resources")]), ] ) diff --git a/Package@swift-5.8.swift b/Package@swift-5.8.swift new file mode 100644 index 00000000..e601c2fe --- /dev/null +++ b/Package@swift-5.8.swift @@ -0,0 +1,54 @@ +// swift-tools-version:5.8 +// The swift-tools-version declares the minimum version of Swift required to build this package. +import PackageDescription + +let defines: [SwiftSetting] = [ + .define("SYSTEM_ICONV", .when(platforms: [.linux])) +] + +#if os(Linux) +let dependencies: [PackageDescription.Package.Dependency] = [ + .package(url: "https://github.com/0xfeedface1993/iconv.git", branch: "main"), + .package(url: "https://github.com/0xfeedface1993/CEnca.git", from: "0.1.3"), +] +let products: [Target.Dependency] = [ + .product(name: "iconv", package: "iconv"), + .product(name: "EncodingWrapper", package: "CEnca"), +] +#else +let dependencies: [PackageDescription.Package.Dependency] = [ + .package(url: "https://github.com/apple/swift-log.git", from: "1.5.2"), +] +let products: [Target.Dependency] = [ + .product(name: "Logging", package: "swift-log"), +] +#endif + +let package = Package( + name: "Zip", + products: [ + .library(name: "Zip", targets: ["Zip"]) + ], + dependencies: dependencies, + targets: [ + .target( + name: "Minizip", + dependencies: [], + path: "Zip/minizip", + exclude: ["module"], + linkerSettings: [ + .linkedLibrary("z") + ]), + .target( + name: "Zip", + dependencies: ["Minizip"] + products, + path: "Zip", + exclude: ["minizip", "zlib"], + swiftSettings: defines), + .testTarget( + name: "ZipTests", + dependencies: ["Zip"], + path: "ZipTests", + resources: [.process("Resources")]), + ] +) diff --git a/Zip.podspec b/Zip.podspec index 907757f9..5842cd03 100644 --- a/Zip.podspec +++ b/Zip.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = "Zip" - s.version = "2.1.2" + s.version = "2.1.3" s.summary = "Zip and unzip files in Swift." s.swift_version = "5.3" s.swift_versions = ["4.2", "5.0", "5.1", "5.3"] diff --git a/Zip/Info-tvOS.plist b/Zip/Info-tvOS.plist index bc21553c..41c0d687 100644 --- a/Zip/Info-tvOS.plist +++ b/Zip/Info-tvOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.2 + 2.1.3 CFBundleSignature ???? CFBundleVersion diff --git a/Zip/Info.plist b/Zip/Info.plist index bc21553c..41c0d687 100644 --- a/Zip/Info.plist +++ b/Zip/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.2 + 2.1.3 CFBundleSignature ???? CFBundleVersion diff --git a/Zip/Zip.swift b/Zip/Zip.swift index af2d453e..ac98281a 100644 --- a/Zip/Zip.swift +++ b/Zip/Zip.swift @@ -160,8 +160,15 @@ public class Zip { unzGetCurrentFileInfo64(zip, &fileInfo, fileName, UInt(fileNameSize), nil, 0, nil, 0) fileName[Int(fileInfo.size_filename)] = 0 - var pathString = String(cString: fileName) - + let encodingPathString = AutoEncodingString(data: .init(bytes: fileName, count: fileNameSize)).text() + var pathString = encodingPathString.isEmpty ? String(cString: fileName):encodingPathString +#if DEBUG + if encodingPathString.isEmpty { + print("encoding string failed, use cString \(pathString)") + } else { + print("encoding string \(pathString)") + } +#endif guard pathString.count > 0 else { throw ZipError.unzipFail } diff --git a/Zip/ZipUtilities.swift b/Zip/ZipUtilities.swift index 0bcec54e..b3369f57 100644 --- a/Zip/ZipUtilities.swift +++ b/Zip/ZipUtilities.swift @@ -7,6 +7,14 @@ // import Foundation +import Logging + +#if SYSTEM_ICONV && canImport(iconv) && canImport(EncodingWrapper) +import iconv +import EncodingWrapper +#endif + +fileprivate let logger = Logger(label: "com.zip.utilties") internal class ZipUtilities { @@ -19,21 +27,21 @@ internal class ZipUtilities { As true: $ zip -r Test.zip Test/ $ unzip -l Test.zip - Test/ - Test/A.txt - Test/B.txt + Test/ + Test/A.txt + Test/B.txt As false: $ zip -r Test.zip Test/ $ unzip -l Test.zip - A.txt - B.txt - */ + A.txt + B.txt + */ let includeRootDirectory = true - + // File manager let fileManager = FileManager.default - + /** * ProcessedFilePath struct */ @@ -49,12 +57,12 @@ internal class ZipUtilities { //MARK: Path processing /** - Process zip paths - - - parameter paths: Paths as NSURL. - - - returns: Array of ProcessedFilePath structs. - */ + Process zip paths + + - parameter paths: Paths as NSURL. + + - returns: Array of ProcessedFilePath structs. + */ internal func processZipPaths(_ paths: [URL]) -> [ProcessedFilePath]{ var processedFilePaths = [ProcessedFilePath]() for path in paths { @@ -88,7 +96,7 @@ internal class ZipUtilities { while let filePathComponent = enumerator.nextObject() as? String { let path = directory.appendingPathComponent(filePathComponent) let filePath = path.path - + var isDirectory: ObjCBool = false _ = fileManager.fileExists(atPath: filePath, isDirectory: &isDirectory) if !isDirectory.boolValue { @@ -104,5 +112,55 @@ internal class ZipUtilities { } return processedFilePaths } +} +#if SYSTEM_ICONV && canImport(iconv) && canImport(EncodingWrapper) +extension Iconv.CodePage { + init(name: String) throws { + guard let sourceCodePage = Iconv.CodePage(rawValue: name) else { + throw CocoaError(.fileReadUnknownStringEncoding) + } + self = sourceCodePage + } +} +#endif + +public struct AutoEncodingString { + /// Raw sting data + public let data: Data + + public init(data: Data) { + self.data = data + } + +#if SYSTEM_ICONV && canImport(iconv) && canImport(EncodingWrapper) + /// Auto detect string encode + /// - Returns: string with valid encoding, it will be empty string if encoding failed. + public func text() -> String { + do { + let encodeName = try EncodingWrapper(data).style(.iconv).guessAllLanguageFoEncodingString() + let sourceCodePage = try Iconv.CodePage(name: encodeName) + var buffer = data.withUnsafeBytes(Array.init(_:)) + if buffer.last != 0 { + buffer.append(0) + } + let unicodeString = try Iconv(from: sourceCodePage, to: .UTF8).utf8(buf: buffer) + return unicodeString ?? "" + } catch { + logger.error("string encoding process failed, \(error)") + return "" + } + } +#else + /// Auto detect string encode + /// - Returns: string with valid encoding, it will be empty string if encoding failed. + public func text() -> String { + var lossy = ObjCBool(false) + let encodingValue = NSString.stringEncoding(for: data, encodingOptions: nil, convertedString: nil, usedLossyConversion: &lossy) + let encoding = String.Encoding(rawValue: encodingValue) + logger.info("detect string encoding \(encoding), usedLossyConversion \(lossy.boolValue)") + let string = String(data: data, encoding: encoding) + return string ?? "" + } +#endif } diff --git a/ZipTests/Info.plist b/ZipTests/Info.plist index db36cdf0..c8548fd8 100644 --- a/ZipTests/Info.plist +++ b/ZipTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.2 + 2.1.3 CFBundleSignature ???? CFBundleVersion diff --git a/ZipTests/Resources/gb2312.zip b/ZipTests/Resources/gb2312.zip new file mode 100644 index 00000000..f5daad75 Binary files /dev/null and b/ZipTests/Resources/gb2312.zip differ diff --git a/ZipTests/ZipTests.swift b/ZipTests/ZipTests.swift index 70f689ff..aad9052b 100644 --- a/ZipTests/ZipTests.swift +++ b/ZipTests/ZipTests.swift @@ -33,7 +33,7 @@ class ZipTests: XCTestCase { private func url(forResource resource: String, withExtension ext: String? = nil) -> URL? { #if Xcode - return Bundle(for: ZipTests.self).url(forResource: resource, withExtension: ext) + return Bundle.module.url(forResource: resource, withExtension: ext) #else let testDirPath = URL(fileURLWithPath: String(#file)).deletingLastPathComponent() let resourcePath = testDirPath.appendingPathComponent("Resources").appendingPathComponent(resource) @@ -271,4 +271,13 @@ class ZipTests: XCTestCase { XCTAssertTrue(Zip.isValidFileExtension("zip")) XCTAssertTrue(Zip.isValidFileExtension("cbz")) } + + func testGB2312File() throws { + let filePath = url(forResource: "gb2312", withExtension: "zip")! + let destinationURL = try Zip.quickUnzipFile(filePath) + addTeardownBlock { + try? FileManager.default.removeItem(at: destinationURL) + } + XCTAssertTrue(FileManager.default.fileExists(atPath: destinationURL.path)) + } }