Skip to content

Commit 8b9030d

Browse files
dnadobaLukasa
andauthored
Enable automatic compression format detection (#208)
Co-authored-by: Cory Benfield <[email protected]>
1 parent 3bbec98 commit 8b9030d

File tree

5 files changed

+70
-25
lines changed

5 files changed

+70
-25
lines changed

Sources/NIOHTTPCompression/HTTPDecompression.swift

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,18 +94,41 @@ public enum NIOHTTPDecompression {
9494
return nil
9595
}
9696
}
97-
98-
var window: CInt {
99-
switch self {
100-
case .deflate:
101-
return 15
102-
case .gzip:
103-
return 15 + 16
104-
}
105-
}
10697
}
10798

10899
struct Decompressor {
100+
/// `15` is the base two logarithm of the maximum window size (the size of the history buffer). It should be in the range 8..15 for this version of the library.
101+
/// `32` enables automatic detection of gzip or zlib compression format with automatic header detection.
102+
///
103+
/// Documentation from https://www.zlib.net/manual.html:
104+
/// The windowBits parameter is the base two logarithm of the maximum window size (the size of the history buffer).
105+
/// It should be in the range 8..15 for this version of the library.
106+
/// The default value is 15 if inflateInit is used instead.
107+
/// windowBits must be greater than or equal to the windowBits value provided to deflateInit2() while compressing,
108+
/// or it must be equal to 15 if deflateInit2() was not used.
109+
/// If a compressed stream with a larger window size is given as input,
110+
/// inflate() will return with the error code Z_DATA_ERROR instead of trying to allocate a larger window.
111+
/// windowBits can also be zero to request that inflate use the window size in the zlib header of the compressed stream.
112+
/// windowBits can also be –8..–15 for raw inflate.
113+
/// In this case, -windowBits determines the window size.
114+
/// inflate() will then process raw deflate data, not looking for a zlib or gzip header, not generating a check value,
115+
/// and not looking for any check values for comparison at the end of the stream.
116+
/// This is for use with other formats that use the deflate compressed data format such as zip.
117+
/// Those formats provide their own check values.
118+
/// If a custom format is developed using the raw deflate format for compressed data,
119+
/// it is recommended that a check value such as an Adler-32 or a CRC-32 be applied to the uncompressed data as is done in the zlib,
120+
/// gzip, and zip formats. For most applications, the zlib format should be used as is.
121+
/// Note that comments above on the use in deflateInit2() applies to the magnitude of windowBits.
122+
/// windowBits can also be greater than 15 for optional gzip decoding.
123+
/// Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection,
124+
/// or add 16 to decode only the gzip format (the zlib format will return a Z_DATA_ERROR).
125+
/// If a gzip stream is being decoded, strm->adler is a CRC-32 instead of an Adler-32.
126+
/// Unlike the gunzip utility and gzread() (see below), inflate() will not automatically decode concatenated gzip members.
127+
/// inflate() will return Z_STREAM_END at the end of the gzip member.
128+
/// The state would need to be reset to continue decoding a subsequent gzip member.
129+
/// This must be done if there is more data after a gzip member, in order for the decompression to be compliant with the gzip standard (RFC 1952).
130+
static let windowBitsWithAutomaticCompressionFormatDetection: Int32 = 15 + 32
131+
109132
private let limit: NIOHTTPDecompression.DecompressionLimit
110133
private var stream = z_stream()
111134
private var inflated = 0
@@ -125,13 +148,13 @@ public enum NIOHTTPDecompression {
125148
return result
126149
}
127150

128-
mutating func initializeDecoder(encoding: NIOHTTPDecompression.CompressionAlgorithm) throws {
151+
mutating func initializeDecoder() throws {
129152
self.stream.zalloc = nil
130153
self.stream.zfree = nil
131154
self.stream.opaque = nil
132155
self.inflated = 0
133156

134-
let rc = CNIOExtrasZlib_inflateInit2(&self.stream, encoding.window)
157+
let rc = CNIOExtrasZlib_inflateInit2(&self.stream, Self.windowBitsWithAutomaticCompressionFormatDetection)
135158
guard rc == Z_OK else {
136159
throw NIOHTTPDecompression.DecompressionError.initializationError(Int(rc))
137160
}

Sources/NIOHTTPCompression/HTTPRequestDecompressor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public final class NIOHTTPRequestDecompressor: ChannelDuplexHandler, RemovableCh
5555
let length = head.headers[canonicalForm: "Content-Length"].first.flatMap({ Int($0) })
5656
{
5757
do {
58-
try self.decompressor.initializeDecoder(encoding: algorithm)
58+
try self.decompressor.initializeDecoder()
5959
self.compression = Compression(algorithm: algorithm, contentLength: length)
6060
} catch let error {
6161
context.fireErrorCaught(error)

Sources/NIOHTTPCompression/HTTPResponseDecompressor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public final class NIOHTTPResponseDecompressor: ChannelDuplexHandler, RemovableC
7171
do {
7272
if let algorithm = algorithm {
7373
self.compression = Compression(algorithm: algorithm, compressedLength: 0)
74-
try self.decompressor.initializeDecoder(encoding: algorithm)
74+
try self.decompressor.initializeDecoder()
7575
}
7676

7777
context.fireChannelRead(data)

Tests/NIOHTTPCompressionTests/HTTPRequestDecompressorTest.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,20 @@ class HTTPRequestDecompressorTest: XCTestCase {
103103
try channel.pipeline.addHandler(NIOHTTPRequestDecompressor(limit: .none)).wait()
104104

105105
let body = Array(repeating: testString, count: 1000).joined()
106-
107-
for algorithm in [nil, "gzip", "deflate"] {
106+
let algorithms: [(actual: String, announced: String)?] = [
107+
nil,
108+
(actual: "gzip", announced: "gzip"),
109+
(actual: "deflate", announced: "deflate"),
110+
(actual: "gzip", announced: "deflate"),
111+
(actual: "deflate", announced: "gzip"),
112+
]
113+
114+
for algorithm in algorithms {
108115
let compressed: ByteBuffer
109116
var headers = HTTPHeaders()
110117
if let algorithm = algorithm {
111-
headers.add(name: "Content-Encoding", value: algorithm)
112-
compressed = compress(ByteBuffer.of(string: body), algorithm)
118+
headers.add(name: "Content-Encoding", value: algorithm.announced)
119+
compressed = compress(ByteBuffer.of(string: body), algorithm.actual)
113120
} else {
114121
compressed = ByteBuffer.of(string: body)
115122
}

Tests/NIOHTTPCompressionTests/HTTPResponseDecompressorTest.swift

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,20 @@ class HTTPResponseDecompressorTest: XCTestCase {
166166
for _ in 1...1000 {
167167
body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
168168
}
169-
170-
for algorithm in [nil, "gzip", "deflate"] {
169+
let algorithms: [(actual: String, announced: String)?] = [
170+
nil,
171+
(actual: "gzip", announced: "gzip"),
172+
(actual: "deflate", announced: "deflate"),
173+
(actual: "gzip", announced: "deflate"),
174+
(actual: "deflate", announced: "gzip"),
175+
]
176+
177+
for algorithm in algorithms {
171178
let compressed: ByteBuffer
172179
var headers = HTTPHeaders()
173180
if let algorithm = algorithm {
174-
headers.add(name: "Content-Encoding", value: algorithm)
175-
compressed = compress(ByteBuffer.of(string: body), algorithm)
181+
headers.add(name: "Content-Encoding", value: algorithm.announced)
182+
compressed = compress(ByteBuffer.of(string: body), algorithm.actual)
176183
} else {
177184
compressed = ByteBuffer.of(string: body)
178185
}
@@ -213,13 +220,21 @@ class HTTPResponseDecompressorTest: XCTestCase {
213220
for _ in 1...1000 {
214221
body += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
215222
}
216-
217-
for algorithm in [nil, "gzip", "deflate"] {
223+
224+
let algorithms: [(actual: String, announced: String)?] = [
225+
nil,
226+
(actual: "gzip", announced: "gzip"),
227+
(actual: "deflate", announced: "deflate"),
228+
(actual: "gzip", announced: "deflate"),
229+
(actual: "deflate", announced: "gzip"),
230+
]
231+
232+
for algorithm in algorithms {
218233
let compressed: ByteBuffer
219234
var headers = HTTPHeaders()
220235
if let algorithm = algorithm {
221-
headers.add(name: "Content-Encoding", value: algorithm)
222-
compressed = compress(ByteBuffer.of(string: body), algorithm)
236+
headers.add(name: "Content-Encoding", value: algorithm.announced)
237+
compressed = compress(ByteBuffer.of(string: body), algorithm.actual)
223238
} else {
224239
compressed = ByteBuffer.of(string: body)
225240
}

0 commit comments

Comments
 (0)