├── .gitignore ├── .swift-version ├── .travis.yml ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── GZIP │ ├── Buffer.swift │ ├── Data.swift │ ├── GzipCompression.swift │ ├── GzipMiddleware.swift │ ├── GzipStream.swift │ ├── Utilities.swift │ └── gzip.swift └── Tests ├── GZIPTests ├── GZIPTests.swift ├── GzipMiddlewareTests.swift └── StreamTests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | Packages/ 38 | .build/ 39 | gzip.xcodeproj 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/screenshots 65 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | slack: zewo:VjyVCCQvTOw9yrbzQysZezD1 3 | os: 4 | - linux 5 | - osx 6 | language: generic 7 | sudo: required 8 | dist: trusty 9 | osx_image: xcode8 10 | install: 11 | - eval "$(curl -sL https://raw.githubusercontent.com/Zewo/Zewo/master/Scripts/Travis/install.sh)" 12 | script: 13 | - bash <(curl -s https://raw.githubusercontent.com/Zewo/Zewo/master/Scripts/Travis/build-test.sh) GZIP 14 | after_success: 15 | - bash <(curl -s https://raw.githubusercontent.com/Zewo/Zewo/master/Scripts/Travis/report-coverage.sh) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Honza Dvorsky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "GZIP", 5 | dependencies: [ 6 | .Package(url: "https://github.com/Zewo/HTTP.git", majorVersion: 0, minor: 14), 7 | .Package(url: "https://github.com/Zewo/CZlib.git", majorVersion: 0, minor: 4), 8 | .Package(url: "https://github.com/Zewo/Axis.git", majorVersion: 0, minor: 14), 9 | 10 | ] 11 | ) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gzip 2 | 3 | [![Build Status](https://travis-ci.org/Zewo/gzip.svg?branch=master)](https://travis-ci.org/Zewo/gzip) 4 | ![Platforms](https://img.shields.io/badge/platforms-Linux%20%7C%20OS%20X-blue.svg) 5 | ![Package Managers](https://img.shields.io/badge/package%20managers-SwiftPM-yellow.svg) 6 | 7 | [![Blog](https://img.shields.io/badge/blog-honzadvorsky.com-green.svg)](http://honzadvorsky.com) 8 | [![Twitter Czechboy0](https://img.shields.io/badge/twitter-czechboy0-green.svg)](http://twitter.com/czechboy0) 9 | 10 | > gzip data compression from Swift, OS X & Linux ready 11 | 12 | # Usage 13 | Works on `Data` or anything [`Gzippable`](https://github.com/czechboy0/gzip/blob/master/Sources/gzip/gzip%2BData.swift#L42-46) 14 | 15 | ```swift 16 | let myData = ... //Data 17 | let myGzipCompressedData = try myData.gzipCompressed() //Data 18 | ... 19 | let myGzipUncompressedData = try myGzipCompressedData.gzipUncompressed() //Data 20 | ... //PROFIT! 21 | ``` 22 | 23 | Middleware to suport gzipped content in `HTTPClient`. 24 | 25 | ```swift 26 | let request = ... //Request 27 | let response = try client.request(request, middleware: [GzipMiddleware()]) 28 | ``` 29 | 30 | Please note, that `GzipMiddleware` should be placed at the end of a chain: 31 | 32 | ```swift 33 | let response = try client.request(request, middleware: [ContentNegotiationMiddleware(mediaTypes: [.json], mode: .client), GzipMiddleware()]) 34 | ``` 35 | 36 | # Details 37 | 38 | As this library uses a SwiftPM-compatible source of [zlib](https://github.com/Zewo/zlib), you don't need to install anything manually before using it. Even though both OS X and Linux have a preinstalled version of `zlib`, unfortunately each has a different version, making its potential use inconsistent. In our case everything is compiled from source, so you can be sure to get the same results everywhere. :100: 39 | 40 | # Installation 41 | 42 | ## Swift Package Manager 43 | 44 | ```swift 45 | .Package(url: "https://github.com/Zewo/gzip.git", majorVersion: 0, minor: 8) 46 | ``` 47 | 48 | :gift_heart: Contributing 49 | ------------ 50 | Please create an issue with a description of your problem or open a pull request with a fix. 51 | 52 | :+1: Thanks 53 | ------ 54 | This project was initially inspired by [NSData+GZIP](https://github.com/1024jp/NSData-GZIP), thank you! 55 | 56 | :v: License 57 | ------- 58 | MIT 59 | 60 | :alien: Author 61 | ------ 62 | Honza Dvorsky - http://honzadvorsky.com, [@czechboy0](http://twitter.com/czechboy0) 63 | 64 | -------------------------------------------------------------------------------- /Sources/GZIP/Buffer.swift: -------------------------------------------------------------------------------- 1 | import Axis 2 | import Foundation 3 | 4 | extension Buffer { 5 | init(_ data: Data) { 6 | let buf = data.withUnsafeBytes { (ptr: UnsafePointer) -> UnsafeBufferPointer in 7 | return UnsafeBufferPointer(start: ptr, count: data.count) 8 | } 9 | 10 | self.init(buf) 11 | } 12 | } 13 | 14 | extension Buffer: Gzippable { 15 | public func gzipCompressed() throws -> Buffer { 16 | return try autoreleasepoolIfAvailable { 17 | guard self.count > 0 else { return Buffer() } 18 | let uncompressor = GzipMode.compress.processor() 19 | try uncompressor.initialize() 20 | let outData = try uncompressor.process(data: Data(self), isLast: true) 21 | 22 | return Buffer(outData) 23 | } 24 | } 25 | 26 | public func gzipUncompressed() throws -> Buffer { 27 | return try autoreleasepoolIfAvailable { 28 | guard self.count > 0 else { return Buffer() } 29 | let uncompressor = GzipMode.uncompress.processor() 30 | try uncompressor.initialize() 31 | let outData = try uncompressor.process(data: Data(self), isLast: true) 32 | return Buffer(outData) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/GZIP/Data.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Data: Gzippable { 4 | public func gzipCompressed() throws -> Data { 5 | return try autoreleasepoolIfAvailable { 6 | guard self.count > 0 else { return Data() } 7 | let uncompressor = GzipCompressor() 8 | try uncompressor.initialize() 9 | let outData = try uncompressor.process(data: self, isLast: true) 10 | return outData 11 | } 12 | } 13 | 14 | public func gzipUncompressed() throws -> Data { 15 | return try autoreleasepoolIfAvailable { 16 | guard self.count > 0 else { return Data() } 17 | let uncompressor = GzipUncompressor() 18 | try uncompressor.initialize() 19 | let outData = try uncompressor.process(data: self, isLast: true) 20 | return outData 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/GZIP/GzipCompression.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Czlib 3 | 4 | private let CHUNK_SIZE: Int = 16384 5 | private let STREAM_SIZE: Int32 = Int32(MemoryLayout.size) 6 | 7 | final class GzipUncompressor: GzipProcessor { 8 | 9 | internal var _stream: UnsafeMutablePointer 10 | internal var closed: Bool = false 11 | 12 | init() { 13 | _stream = _makeStream() 14 | } 15 | 16 | func initialize() throws { 17 | let result = inflateInit2_( 18 | &_stream.pointee, 19 | MAX_WBITS + 32, //+32 to detect gzip header 20 | ZLIB_VERSION, 21 | STREAM_SIZE 22 | ) 23 | guard result == Z_OK else { 24 | throw GzipError(code: result, message: _stream.pointee.msg) 25 | } 26 | } 27 | 28 | func process(data: Data, isLast: Bool) throws -> Data { 29 | let mode = isLast ? Z_FINISH : Z_SYNC_FLUSH 30 | let processChunk: () -> Int32 = { inflate(&self._stream.pointee, mode) } 31 | let loop: (Int32) -> Bool = { _ in self._stream.pointee.avail_in > 0 } 32 | let shouldEnd: (Int32) -> Bool = { _ in isLast } 33 | let end: () -> () = { inflateEnd(&self._stream.pointee) } 34 | return try self._process(data: data, processChunk: processChunk, loop: loop, shouldEnd: shouldEnd, end: end) 35 | } 36 | 37 | func close() { 38 | if !closed { 39 | inflateEnd(&_stream.pointee) 40 | closed = true 41 | } 42 | } 43 | 44 | deinit { 45 | close() 46 | _clearMemory() 47 | } 48 | } 49 | 50 | final class GzipCompressor: GzipProcessor { 51 | 52 | internal var _stream: UnsafeMutablePointer 53 | internal var closed: Bool = false 54 | 55 | init() { 56 | _stream = _makeStream() 57 | } 58 | 59 | func initialize() throws { 60 | let result = deflateInit2_( 61 | &_stream.pointee, 62 | Z_DEFAULT_COMPRESSION, 63 | Z_DEFLATED, 64 | MAX_WBITS + 16, //+16 to specify gzip header 65 | MAX_MEM_LEVEL, 66 | Z_DEFAULT_STRATEGY, 67 | ZLIB_VERSION, 68 | STREAM_SIZE 69 | ) 70 | guard result == Z_OK else { 71 | throw GzipError(code: result, message: _stream.pointee.msg) 72 | } 73 | } 74 | 75 | func process(data: Data, isLast: Bool) throws -> Data { 76 | let mode = isLast ? Z_FINISH : Z_SYNC_FLUSH 77 | let processChunk: () -> Int32 = { 78 | deflate(&self._stream.pointee, mode) } 79 | let loop: (Int32) -> Bool = { _ in self._stream.pointee.avail_out == 0 } 80 | let shouldEnd: (_ result: Int32) -> Bool = { _ in isLast } 81 | let end: () -> () = { deflateEnd(&self._stream.pointee) } 82 | return try self._process(data: data, processChunk: processChunk, loop: loop, shouldEnd: shouldEnd, end: end) 83 | } 84 | 85 | func close() { 86 | if !closed { 87 | deflateEnd(&_stream.pointee) 88 | closed = true 89 | } 90 | } 91 | 92 | deinit { 93 | close() 94 | _clearMemory() 95 | } 96 | } 97 | 98 | func _makeStream() -> UnsafeMutablePointer { 99 | 100 | let stream = z_stream(next_in: nil, avail_in: 0, total_in: 0, next_out: nil, avail_out: 0, total_out: 0, msg: nil, state: nil, zalloc: nil, zfree: nil, opaque: nil, data_type: 0, adler: 0, reserved: 0) 101 | let ptr = UnsafeMutablePointer.allocate(capacity: 1) 102 | ptr.initialize(to: stream) 103 | return ptr 104 | } 105 | 106 | extension GzipProcessor { 107 | 108 | func _clearMemory() { 109 | _stream.deinitialize(count: 1) 110 | _stream.deallocate(capacity: 1) 111 | } 112 | 113 | func _process(data: Data, 114 | processChunk: () -> Int32, 115 | loop: (Int32) -> Bool, 116 | shouldEnd: (Int32) -> Bool, 117 | end: () -> ()) throws -> Data { 118 | guard data.count > 0 else { return Data() } 119 | 120 | _stream.pointee.next_in = data.withUnsafeBytes { (input: UnsafePointer) in UnsafeMutablePointer(mutating: input) } 121 | 122 | _stream.pointee.avail_in = uInt(data.count) 123 | 124 | var output = Data(capacity: CHUNK_SIZE) 125 | output.count = CHUNK_SIZE 126 | 127 | let chunkStart = _stream.pointee.total_out 128 | 129 | var result: Int32 = 0 130 | repeat { 131 | 132 | if _stream.pointee.total_out >= uLong(output.count) { 133 | output.count += CHUNK_SIZE; 134 | } 135 | 136 | let writtenThisChunk = _stream.pointee.total_out - chunkStart 137 | let availOut = uLong(output.count) - writtenThisChunk 138 | _stream.pointee.avail_out = uInt(availOut) 139 | _stream.pointee.next_out = output.withUnsafeMutableBytes{ (out: UnsafeMutablePointer) in 140 | out.advanced(by: Int(writtenThisChunk)) 141 | } 142 | 143 | result = processChunk() 144 | guard result >= 0 || (result == Z_BUF_ERROR && _stream.pointee.avail_out == 0) else { 145 | throw GzipError(code: result, message: _stream.pointee.msg) 146 | } 147 | 148 | } while loop(result) 149 | 150 | guard result == Z_STREAM_END || result == Z_OK else { 151 | throw GzipError.stream(message: "Wrong result code \(result)") 152 | } 153 | if shouldEnd(result) { 154 | end() 155 | closed = true 156 | } 157 | let chunkCount = _stream.pointee.total_out - chunkStart 158 | output.count = Int(chunkCount) 159 | return output 160 | } 161 | } 162 | 163 | 164 | -------------------------------------------------------------------------------- /Sources/GZIP/GzipMiddleware.swift: -------------------------------------------------------------------------------- 1 | import HTTP 2 | import Foundation 3 | 4 | public enum GzipMiddlewareError: Error { 5 | case unsupportedStreamType 6 | } 7 | 8 | /// Middleware for decompressing gzipped data 9 | /// Adds HTTP header `Accept-Encoding: gzip` and work only if response 10 | /// header `Content-Encoding` contains `gzip` 11 | /// Note: this middleware should we at the end of a middleware chain. 12 | /// let request = Request(method: .get, url: "/gzipped")! 13 | /// let response = try client.request(request, middleware: [ContentNegotiationMiddleware(mediaTypes: [.json], mode: .client), GzipMiddleware()]) 14 | public struct GzipMiddleware: Middleware { 15 | 16 | public init() { } 17 | 18 | public func respond(to request: Request, chainingTo next: Responder) throws -> Response { 19 | 20 | var req = request 21 | req.headers["Accept-Encoding"] = "gzip" 22 | 23 | var response = try next.respond(to: req) 24 | 25 | guard response.headers["Content-Encoding"]?.contains("gzip") ?? false else { return response } 26 | 27 | let zipped = response.body 28 | switch zipped { 29 | case .buffer(let data): 30 | let uncompressedData = try data.gzipUncompressed() 31 | response.body = .buffer(uncompressedData) 32 | case .reader(let stream): 33 | let uncompressedStream = try GzipStream(rawStream: stream, mode: .uncompress) 34 | response.body = .reader(uncompressedStream) 35 | default: 36 | throw GzipMiddlewareError.unsupportedStreamType 37 | } 38 | return response 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/GZIP/GzipStream.swift: -------------------------------------------------------------------------------- 1 | import Axis 2 | import Foundation 3 | 4 | public final class GzipStream: Axis.InputStream { 5 | 6 | private let rawStream: Axis.InputStream 7 | private let processor: GzipProcessor 8 | 9 | public private (set) var closed: Bool = false 10 | 11 | public init(rawStream: Axis.InputStream, mode: GzipMode) throws { 12 | self.rawStream = rawStream 13 | self.processor = mode.processor() 14 | try self.processor.initialize() 15 | } 16 | 17 | public func close() { 18 | processor.close() 19 | rawStream.close() 20 | self.closed = true 21 | } 22 | 23 | public func open(deadline: Double) throws {} 24 | 25 | public func read(into readBuffer: UnsafeMutableBufferPointer, deadline: Double) throws -> UnsafeBufferPointer { 26 | 27 | guard !closed, let readPointer = readBuffer.baseAddress else { 28 | return UnsafeBufferPointer() 29 | } 30 | 31 | let chunk = try rawStream.read(upTo: readBuffer.count, deadline: deadline) 32 | 33 | if processor.closed { 34 | throw GzipError.unknown(message: "Gzip stream already closed", code: 10) 35 | } 36 | 37 | let isLast = rawStream.closed || processor.closed 38 | let outputData = try processor.process(data: Data(chunk.bytes), isLast: isLast) 39 | 40 | let count = outputData.copyBytes(to: readBuffer) 41 | 42 | if rawStream.closed { 43 | processor.close() 44 | closed = true 45 | } 46 | 47 | return UnsafeBufferPointer(start: readPointer, count: count) 48 | } 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /Sources/GZIP/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities.swift 3 | // gzip 4 | // 5 | // Created by Honza Dvorsky on 5/23/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | func autoreleasepoolIfAvailable(_ body: () throws -> Result) rethrows -> Result { 12 | #if _runtime(_ObjC) 13 | return try autoreleasepool(invoking: body) 14 | #else 15 | return try body() 16 | #endif 17 | } 18 | -------------------------------------------------------------------------------- /Sources/GZIP/gzip.swift: -------------------------------------------------------------------------------- 1 | import Czlib 2 | import Foundation 3 | 4 | public enum GzipMode { 5 | case compress 6 | case uncompress 7 | 8 | public func processor() -> GzipProcessor { 9 | switch self { 10 | case .compress: return GzipCompressor() 11 | case .uncompress: return GzipUncompressor() 12 | } 13 | } 14 | } 15 | 16 | public protocol Gzippable { 17 | associatedtype DataType 18 | /// returns compressed buffer or throws an `Error`. 19 | /// 20 | /// - throws: An `Error` if the buffer can not be compressed. 21 | /// 22 | /// - returns: A Buffer with compressed data or throws an `Error`. 23 | func gzipCompressed() throws -> DataType 24 | 25 | /// returns uncompressed buffer or throws an `Error`. 26 | /// 27 | /// - throws: An `Error` if the buffer can not be decompressed. 28 | /// 29 | /// - returns: A Buffer with uncompressed data or throws an `Error`. 30 | func gzipUncompressed() throws -> DataType 31 | } 32 | 33 | public protocol GzipProcessor: class { 34 | func initialize() throws 35 | func process(data: Data, isLast: Bool) throws -> Data 36 | func close() 37 | var closed: Bool { get set } 38 | var _stream: UnsafeMutablePointer { get } 39 | } 40 | 41 | private let CHUNK_SIZE: Int = 2 ^ 14 42 | private let STREAM_SIZE: Int32 = Int32(MemoryLayout.size) 43 | 44 | public enum GzipError: Error { 45 | //Reference: http://www.zlib.net/manual.html 46 | 47 | /// The stream structure was inconsistent. 48 | case stream(message: String) 49 | 50 | ///The input data was corrupted (input stream not conforming to the zlib format or incorrect check value). 51 | case data(message: String) 52 | 53 | /// There was not enough memory. 54 | case memory(message: String) 55 | 56 | /// No progress is possible or there was not enough room in the output buffer. 57 | case buffer(message: String) 58 | 59 | /// The zlib library version is incompatible with the version assumed by the caller. 60 | case version(message: String) 61 | 62 | /// An unknown error occurred. 63 | case unknown(message: String, code: Int) 64 | 65 | internal init(code: Int32, message cmessage: UnsafePointer?) 66 | { 67 | let message: String 68 | if let cmessage = cmessage, let msg = String(validatingUTF8: cmessage) { 69 | message = msg 70 | } else { 71 | message = "unknown gzip error" 72 | } 73 | switch code { 74 | case Z_STREAM_ERROR: self = .stream(message: message) 75 | case Z_DATA_ERROR: self = .data(message: message) 76 | case Z_MEM_ERROR: self = .memory(message: message) 77 | case Z_BUF_ERROR: self = .buffer(message: message) 78 | case Z_VERSION_ERROR: self = .version(message: message) 79 | default: self = .unknown(message: message, code: Int(code)) 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /Tests/GZIPTests/GZIPTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | @testable import GZIP 4 | 5 | class GZIPTests: XCTestCase { 6 | 7 | func testCompressAndUncompress_Data() throws { 8 | let inputString = "hello world hello world hello world hello world hello errbody" 9 | let input = inputString.toData() 10 | let output = try input.gzipCompressed() 11 | let recoveredInput = try output.gzipUncompressed() 12 | let recoveredString = recoveredInput.toString() 13 | XCTAssertEqual(recoveredString, inputString) 14 | } 15 | 16 | func testEmpty() throws { 17 | let inputString = "" 18 | let input = inputString.toData() 19 | let output = try input.gzipCompressed() 20 | XCTAssertEqual(output.count, 0) 21 | let recoveredInput = try output.gzipUncompressed() 22 | let recoveredString = recoveredInput.toString() 23 | XCTAssertEqual(recoveredString, inputString) 24 | } 25 | 26 | func testDecompress_IncorrectData() throws { 27 | let inputString = "foo" 28 | let input = inputString.toData() 29 | do { 30 | _ = try input.gzipUncompressed() 31 | } catch GzipError.data(message: let message) { 32 | //all good 33 | XCTAssertEqual(message, "incorrect header check") 34 | return 35 | } 36 | XCTFail("Should have thrown") 37 | } 38 | 39 | func testUncompressGzip_Fixture() throws { 40 | let data = Data(base64Encoded: "H4sICElFQ1cAA2ZpbGUudHh0AMtIzcnJVyjPL8pJUUjLz1dISiwC00DMBQBN/m/HHAAAAA==", options: [])! 41 | let output = try data.gzipUncompressed() 42 | let outputString = output.toString() 43 | XCTAssertEqual(outputString, "hello world foo bar foo foo\n") 44 | } 45 | 46 | func testCompressGzip_Fixture() throws { 47 | let data = "hello world foo bar foo foo\n".data(using: String.Encoding.utf8)! 48 | let output = try data.gzipCompressed() 49 | #if os(Linux) 50 | let outputString = output.base64EncodedString(options: []) 51 | #else 52 | let outputString = output.base64EncodedString(options: []) 53 | #endif 54 | XCTAssertEqual(outputString, "H4sIAAAAAAAAA8tIzcnJVyjPL8pJUUjLz1dISiwC00DMBQBN/m/HHAAAAA==") 55 | } 56 | 57 | func testPerformance_Data() throws { 58 | let inputString = Array(repeating: "hello world ", count: 100000).joined(separator: ", ") 59 | let input = inputString.toData() 60 | 61 | measure { 62 | let output = try! input.gzipCompressed() 63 | _ = try! output.gzipUncompressed() 64 | } 65 | } 66 | 67 | func testPerformance_FoundationData() throws { 68 | let inputString = Array(repeating: "hello world ", count: 100000).joined(separator: ", ") 69 | let input: Foundation.Data = inputString.toData() 70 | 71 | measure { 72 | let output = try! input.gzipCompressed() 73 | _ = try! output.gzipUncompressed() 74 | } 75 | } 76 | 77 | let unzippedString = "hello world foo bar foo foo" 78 | var unzippedData: Data { 79 | return unzippedString.data(using: String.Encoding.utf8)! 80 | } 81 | let zippedString = "H4sIAAAAAAAA/8tIzcnJVyjPL8pJUUjLz1dISiwC00AMAFeJPLcbAAAA" 82 | var zippedData: Data { 83 | return Data(base64Encoded: zippedString)! 84 | } 85 | 86 | func testChunks_compress() throws { 87 | let data = unzippedData //27 bytes 88 | let processor = GzipMode.compress.processor() 89 | try processor.initialize() 90 | var outData: Data = Data() 91 | let chunkSize = 5 92 | let rounds = Int(floor(Double(data.count) / Double(chunkSize))) 93 | for i in 0...rounds { 94 | let end = min((i+1)*chunkSize, data.count) 95 | let chunk = data.subdata(i*chunkSize..) -> Data { 174 | let sub: [UInt8] = Array(self[range]) 175 | return Data(bytes: sub) 176 | } 177 | } 178 | 179 | extension String { 180 | func toData() -> Foundation.Data { 181 | return self.data(using: String.Encoding.utf8) ?? Foundation.Data() 182 | } 183 | } 184 | 185 | extension Foundation.Data { 186 | func toString() -> String { 187 | return String(data: self, encoding: String.Encoding.utf8) ?? "" 188 | } 189 | } 190 | 191 | extension GZIPTests { 192 | public static var allTests: [(String, (GZIPTests) -> () throws -> Void)] { 193 | return [ 194 | ("testCompressAndUncompress_Data", testCompressAndUncompress_Data), 195 | ("testEmpty", testEmpty), 196 | ("testDecompress_IncorrectData", testDecompress_IncorrectData), 197 | ("testUncompressGzip_Fixture", testUncompressGzip_Fixture), 198 | ("testCompressGzip_Fixture", testCompressGzip_Fixture), 199 | ("testPerformance_Data", testPerformance_Data), 200 | ("testPerformance_FoundationData", testPerformance_FoundationData), 201 | ("testChunks_compress", testChunks_compress), 202 | // ("testChunks_compress_flushWithEmpty", testChunks_compress_flushWithEmpty), 203 | ("testChunks_uncompress", testChunks_uncompress) 204 | ] 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Tests/GZIPTests/GzipMiddlewareTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import HTTP 3 | @testable import GZIP 4 | 5 | public class GzipMiddlewareTests : XCTestCase { 6 | let middleware = GzipMiddleware() 7 | 8 | func testHeaderAdded() throws { 9 | let request = Request() 10 | 11 | _ = try middleware.respond(to: request, chainingTo: BasicResponder { request in 12 | XCTAssert(request.headers.contains{ 13 | $0.key.string == "Accept-Encoding" && $0.value == "gzip" 14 | }) 15 | return Response() 16 | }) 17 | } 18 | 19 | func testGzipBuffer() throws { 20 | let request = Request() 21 | let inputData = "H4sICElFQ1cAA2ZpbGUudHh0AMtIzcnJVyjPL8pJUUjLz1dISiwC00DMBQBN/m/HHAAAAA==".fromBase64() 22 | 23 | let response = try middleware.respond(to: request, chainingTo: BasicResponder { request in 24 | return Response(headers: ["Content-Encoding": "gzip"], body: Body.buffer(Buffer(inputData))) 25 | }) 26 | 27 | switch response.body { 28 | case .buffer(let buf): 29 | XCTAssertEqual(String(buf), "hello world foo bar foo foo\n") 30 | default: 31 | XCTFail() 32 | } 33 | } 34 | 35 | func testGzipStream() throws { 36 | let request = Request() 37 | let inputData = "H4sICElFQ1cAA2ZpbGUudHh0AMtIzcnJVyjPL8pJUUjLz1dISiwC00DMBQBN/m/HHAAAAA==".fromBase64() 38 | 39 | let response = try middleware.respond(to: request, chainingTo: BasicResponder { request in 40 | return Response(headers: ["Content-Encoding": "gzip"], body: Body.reader(DrainStream(inputData))) 41 | }) 42 | 43 | switch response.body { 44 | case .reader(let stream): 45 | let buf = try stream.drain(deadline: .never) 46 | XCTAssertEqual(String(buf), "hello world foo bar foo foo\n") 47 | default: 48 | XCTFail() 49 | } 50 | } 51 | } 52 | 53 | extension GzipMiddlewareTests { 54 | public static var allTests: [(String, (GzipMiddlewareTests) -> () throws -> Void)] { 55 | return [ 56 | ("testHeaderAdded", testHeaderAdded), 57 | ("testGzipBuffer", testGzipBuffer), 58 | ("testGzipStream", testGzipStream), 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/GZIPTests/StreamTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | import Axis 4 | @testable import GZIP 5 | 6 | class StreamTests: XCTestCase { 7 | 8 | func testCompressAndUncompress_Data() throws { 9 | let inputString = "hello world foo bar foo foo\n" 10 | let input: Data = inputString.data 11 | let output = try input.gzipCompressed() 12 | let recoveredInput = try output.gzipUncompressed() 13 | let recoveredString = String(recoveredInput) 14 | XCTAssertEqual(recoveredString, inputString) 15 | } 16 | 17 | func testStream_Uncompress_Data() throws { 18 | let inputData = "H4sICElFQ1cAA2ZpbGUudHh0AMtIzcnJVyjPL8pJUUjLz1dISiwC00DMBQBN/m/HHAAAAA==".fromBase64() 19 | let sourceStream = DrainStream(inputData) 20 | let outStream = try GzipStream(rawStream: sourceStream, mode: .uncompress) 21 | let outData = try outStream.drain(deadline: .never) 22 | let outputString = String(outData) 23 | XCTAssertEqual(outputString, "hello world foo bar foo foo\n") 24 | } 25 | 26 | func testStream_Compress_Data() throws { 27 | let inputData = "hello world foo bar foo foo\n".data 28 | let sourceStream = DrainStream(inputData) 29 | let outStream = try GzipStream(rawStream: sourceStream, mode: .compress) 30 | let outData = try outStream.drain(deadline: .never).data 31 | let outputString = outData.base64EncodedString(options: []) 32 | 33 | XCTAssertEqual(outputString, "H4sIAAAAAAAAA8tIzcnJVyjPL8pJUUjLz1dISiwC00DMBQBN/m/HHAAAAA==") 34 | } 35 | 36 | func testLarge_Stream_Identity() throws { 37 | let inputString = Array(repeating: "hello world ", count: 3000).joined(separator: ", ") 38 | let inputData = inputString.data 39 | let input = DrainStream(inputData) 40 | let compressStream = try GzipStream(rawStream: input, mode: .compress) 41 | let uncompressStream = try GzipStream(rawStream: compressStream, mode: .uncompress) 42 | let outputData = try uncompressStream.drain(deadline: .never).data 43 | let outputString = String(outputData) 44 | XCTAssertEqual(inputString, outputString) 45 | } 46 | 47 | func testPerformance_Data() throws { 48 | let inputString = Array(repeating: "hello world ", count: 100000).joined(separator: ", ") 49 | let input: Data = inputString.data 50 | 51 | measure { 52 | let output = try! input.gzipCompressed() 53 | _ = try! output.gzipUncompressed() 54 | } 55 | } 56 | } 57 | 58 | extension Data { 59 | var buffer: Buffer { 60 | var buf: Buffer = Buffer() 61 | 62 | self.withUnsafeBytes { (b: UnsafePointer) in 63 | let bb = UnsafeBufferPointer(start: b, count: self.count) 64 | buf = Buffer(bb) 65 | } 66 | 67 | return buf 68 | } 69 | 70 | var string: String { 71 | return String(data: self, encoding: String.Encoding.utf8) ?? "" 72 | } 73 | } 74 | 75 | extension Buffer { 76 | var data: Data { 77 | return withUnsafeBytes { (ptr: UnsafePointer) in 78 | let buf = UnsafeBufferPointer(start: ptr, count: self.count) 79 | return Data(buffer: buf) 80 | } 81 | } 82 | } 83 | 84 | 85 | extension String { 86 | var data: Data { 87 | return self.data(using: String.Encoding.utf8)! 88 | } 89 | 90 | init(_ data: Data) { 91 | self.init(data: data, encoding: String.Encoding.utf8)! 92 | } 93 | 94 | init(_ buffer: Buffer) { 95 | 96 | let data = buffer.withUnsafeBytes { (buf: UnsafePointer) in 97 | Data(bytes: buf, count: buffer.count) 98 | } 99 | 100 | self.init(data: data, encoding: String.Encoding.utf8)! 101 | } 102 | 103 | func fromBase64() -> Data { 104 | return Data(base64Encoded: self, options: [])! 105 | } 106 | } 107 | 108 | public final class DrainStream: Axis.InputStream { 109 | private var buffer: Data 110 | public private (set) var closed: Bool = false 111 | 112 | public func open(deadline: Double) throws { 113 | 114 | } 115 | 116 | public init(_ buffer: Data) { 117 | self.buffer = buffer 118 | } 119 | 120 | public func read(into readBuffer: UnsafeMutableBufferPointer, deadline: Double) throws -> UnsafeBufferPointer { 121 | 122 | let byteCount = buffer.count 123 | let result: Data 124 | 125 | if readBuffer.count >= byteCount { 126 | result = buffer 127 | close() 128 | } 129 | else { 130 | result = Data(buffer[0.. () throws -> Void)] { 146 | return [ 147 | ("testCompressAndUncompress_Data", testCompressAndUncompress_Data), 148 | ("testStream_Uncompress_Data", testStream_Uncompress_Data), 149 | ("testStream_Compress_Data", testStream_Compress_Data), 150 | ("testLarge_Stream_Identity", testLarge_Stream_Identity), 151 | ("testPerformance_Data", testPerformance_Data) 152 | ] 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import GZIPTests 3 | 4 | XCTMain([ 5 | testCase(GZIPTests.allTests), 6 | testCase(GzipMiddlewareTests.allTests), 7 | testCase(StreamTests.allTests), 8 | ]) 9 | --------------------------------------------------------------------------------