| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- //
- // DataGzip.swift
- //
- /*
- The MIT License (MIT)
- © 2014-2019 1024jp <wolfrosch.com>
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
- import Foundation
- import Binary
- #if os(Linux)
- import zlibLinux
- #else
- import zlib
- #endif
- /// Compression level whose rawValue is based on the zlib's constants.
- public struct CompressionLevel: RawRepresentable {
- public static let noCompression = CompressionLevel(rawValue: Z_NO_COMPRESSION)
- public static let bestSpeed = CompressionLevel(rawValue: Z_BEST_SPEED)
- public static let bestCompression = CompressionLevel(rawValue: Z_BEST_COMPRESSION)
- public static let defaultCompression = CompressionLevel(rawValue: Z_DEFAULT_COMPRESSION)
- /// Compression level in the range of `0` (no compression) to `9` (maximum compression).
- public let rawValue: Int32
- public init(rawValue: Int32) {
- self.rawValue = rawValue
- }
- }
- /// Errors on gzipping/gunzipping based on the zlib error codes.
- public struct GzipError: Swift.Error {
- // cf. http://www.zlib.net/manual.html
- public enum Kind: Equatable {
- /// The stream structure was inconsistent.
- ///
- /// - underlying zlib error: `Z_STREAM_ERROR` (-2)
- case stream
- /// The input data was corrupted
- /// (input stream not conforming to the zlib format or incorrect check value).
- ///
- /// - underlying zlib error: `Z_DATA_ERROR` (-3)
- case data
- /// There was not enough memory.
- ///
- /// - underlying zlib error: `Z_MEM_ERROR` (-4)
- case memory
- /// No progress is possible or there was not enough room in the output buffer.
- ///
- /// - underlying zlib error: `Z_BUF_ERROR` (-5)
- case buffer
- /// The zlib library version is incompatible with the version assumed by the caller.
- ///
- /// - underlying zlib error: `Z_VERSION_ERROR` (-6)
- case version
- /// An unknown error occurred.
- ///
- /// - parameter code: return error by zlib
- case unknown(code: Int)
- }
- /// Error kind.
- public let kind: Kind
- /// Returned message by zlib.
- public let message: String
- internal init(code: Int32, msg: UnsafePointer<CChar>?) {
- self.message = {
- guard let msg = msg, let message = String(validatingUTF8: msg) else {
- return "Unknown gzip error"
- }
- return message
- }()
- self.kind = {
- switch code {
- case Z_STREAM_ERROR:
- return .stream
- case Z_DATA_ERROR:
- return .data
- case Z_MEM_ERROR:
- return .memory
- case Z_BUF_ERROR:
- return .buffer
- case Z_VERSION_ERROR:
- return .version
- default:
- return .unknown(code: Int(code))
- }
- }()
- }
- public var localizedDescription: String { message }
- }
- extension Bytes {
- /// Whether the receiver is compressed in gzip format.
- public var isGzipped: Bool {
- starts(with: [0x1f, 0x8b]) // check magic number
- }
- /// Create a new `Data` object by compressing the receiver using zlib.
- /// Throws an error if compression failed.
- ///
- /// - Parameter level: Compression level.
- /// - Returns: Gzip-compressed `Data` object.
- /// - Throws: `GzipError`
- public func gzipped(level: CompressionLevel = .defaultCompression) throws -> Bytes {
- guard !isEmpty else { return Bytes() }
- var stream = z_stream()
- var status: Int32
- status = deflateInit2_(&stream,
- level.rawValue,
- Z_DEFLATED, MAX_WBITS + 16,
- MAX_MEM_LEVEL,
- Z_DEFAULT_STRATEGY,
- ZLIB_VERSION,
- Int32(DataSize.stream))
- guard status == Z_OK else {
- // deflateInit2 returns:
- // Z_VERSION_ERROR The zlib library version is incompatible with the version assumed by the caller.
- // Z_MEM_ERROR There was not enough memory.
- // Z_STREAM_ERROR A parameter is invalid.
- throw GzipError(code: status, msg: stream.msg)
- }
- var out = Bytes(lenght: DataSize.chunk)
- repeat {
- if Int(stream.total_out) >= out.count {
- out += Bytes(lenght: DataSize.chunk)
- }
- let outputCount = out.count
- try withUnsafeBytes { input in
- guard let pointer = input.bindMemory(to: Bytef.self).baseAddress else {
- throw GzipError.init(code: 0, msg: nil)
- }
- stream.next_in = UnsafeMutablePointer<Bytef>(mutating: pointer).advanced(by: Int(stream.total_in))
- stream.avail_in = uInt(count) - uInt(stream.total_in)
- try out.withUnsafeMutableBytes { output in
- guard let pointer = output.bindMemory(to: Bytef.self).baseAddress else {
- throw GzipError.init(code: 0, msg: nil)
- }
- stream.next_out = pointer.advanced(by: Int(stream.total_out))
- stream.avail_out = uInt(outputCount) - uInt(stream.total_out)
- status = deflate(&stream, Z_FINISH)
- stream.next_out = nil
- }
- stream.next_in = nil
- }
- } while stream.avail_out == 0
- guard deflateEnd(&stream) == Z_OK, status == Z_STREAM_END else {
- throw GzipError(code: status, msg: stream.msg)
- }
- return out.prefix(Int(stream.total_out))
- }
- /// Create a new `Data` object by decompressing the receiver using zlib.
- /// Throws an error if decompression failed.
- ///
- /// - Returns: Gzip-decompressed `Data` object.
- /// - Throws: `GzipError`
- public func gunzipped() throws -> Bytes {
- guard !isEmpty else { return Bytes() }
- var stream = z_stream()
- var status: Int32
- status = inflateInit2_(&stream, MAX_WBITS + 32, ZLIB_VERSION, Int32(DataSize.stream))
- guard status == Z_OK else {
- // inflateInit2 returns:
- // Z_VERSION_ERROR The zlib library version is incompatible with the version assumed by the caller.
- // Z_MEM_ERROR There was not enough memory.
- // Z_STREAM_ERROR A parameters are invalid.
- throw GzipError(code: status, msg: stream.msg)
- }
- var out = Bytes(lenght: count * 2)
- repeat {
- if Int(stream.total_out) >= out.count {
- out += Bytes(lenght: count * 2)
- }
- let outputCount = out.count
- try withUnsafeBytes { input in
- guard let pointer = input.bindMemory(to: Bytef.self).baseAddress else {
- throw GzipError.init(code: 0, msg: nil)
- }
- stream.next_in = UnsafeMutablePointer<Bytef>(mutating: pointer).advanced(by: Int(stream.total_in))
-
- stream.avail_in = uInt(count) - uInt(stream.total_in)
- try out.withUnsafeMutableBytes { output in
- guard let pointer = output.bindMemory(to: Bytef.self).baseAddress else {
- throw GzipError.init(code: 0, msg: nil)
- }
- stream.next_out = pointer.advanced(by: Int(stream.total_out))
- stream.avail_out = uInt(outputCount) - uInt(stream.total_out)
- status = inflate(&stream, Z_SYNC_FLUSH)
- stream.next_out = nil
- }
- stream.next_in = nil
- }
- } while status == Z_OK
- guard inflateEnd(&stream) == Z_OK, status == Z_STREAM_END else {
- // inflate returns:
- // Z_DATA_ERROR The input data was corrupted (input stream not conforming to the zlib format or incorrect check value).
- // Z_STREAM_ERROR The stream structure was inconsistent (for example if next_in or next_out was NULL).
- // Z_MEM_ERROR There was not enough memory.
- // Z_BUF_ERROR No progress is possible or there was not enough room in the output buffer when Z_FINISH is used.
- throw GzipError(code: status, msg: stream.msg)
- }
- return out.prefix(Int(stream.total_out))
- }
- }
- private struct DataSize {
- static let chunk = 1 << 14
- static let stream = MemoryLayout<z_stream>.size
- }
|