Skip to content

Commit

Permalink
More lenient gzip decompression
Browse files Browse the repository at this point in the history
Be explicitly lenient with gzip decompression by always requesting
`zlib` to flush the input data and never explicitly ending the
`zlib` input.

The behavioural difference is that on Node ≥ 6, which has a slightly
stricter gzip decoding process than previous Node versions, malformed
but otherwise acceptable server responses are still properly
decompressed (the most common example being a missing checksum
at the stream end).

This aligns behaviour with cURL, which always uses the `Z_SYNC_FLUSH`
flag for decompression.

On the downside, accidental truncation of a response is no longer
detected on the compression layer.

Ref: nodejs/node#8701 (comment)
  • Loading branch information
addaleax committed Dec 21, 2016
1 parent 76eb485 commit 25dfff5
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 2 deletions.
13 changes: 11 additions & 2 deletions request.js
Expand Up @@ -946,11 +946,20 @@ Request.prototype.onRequestResponse = function (response) {
var contentEncoding = response.headers['content-encoding'] || 'identity'
contentEncoding = contentEncoding.trim().toLowerCase()

// Be more lenient with decoding compressed responses, since (very rarely)
// servers send slightly invalid gzip responses that are still accepted
// by common browsers.
// Always using Z_SYNC_FLUSH is what cURL does.
var zlibOptions = {
flush: zlib.Z_SYNC_FLUSH
, finishFlush: zlib.Z_SYNC_FLUSH
}

if (contentEncoding === 'gzip') {
responseContent = zlib.createGunzip()
responseContent = zlib.createGunzip(zlibOptions)
response.pipe(responseContent)
} else if (contentEncoding === 'deflate') {
responseContent = zlib.createInflate()
responseContent = zlib.createInflate(zlibOptions)
response.pipe(responseContent)
} else {
// Since previous versions didn't check for Content-Encoding header,
Expand Down
16 changes: 16 additions & 0 deletions tests/test-gzip.js
Expand Up @@ -39,6 +39,12 @@ var server = http.createServer(function(req, res) {
res.writeHead(200)
res.write(testContentBigGzip.slice(0, 4096))
setTimeout(function() { res.end(testContentBigGzip.slice(4096)) }, 10)
} else if (req.url === '/just-slightly-truncated') {
zlib.gzip(testContent, function(err, data) {
assert.equal(err, null)
// truncate the CRC checksum at the end of the stream
res.end(data.slice(0, data.length-1))
})
} else {
zlib.gzip(testContent, function(err, data) {
assert.equal(err, null)
Expand Down Expand Up @@ -96,6 +102,16 @@ tape('transparently supports gzip decoding to callbacks', function(t) {
})
})

tape('supports slightly invalid gzip content', function(t) {
var options = { url: server.url + '/just-slightly-truncated', gzip: true }
request.get(options, function(err, res, body) {
t.equal(err, null)
t.equal(res.headers['content-encoding'], 'gzip')
t.equal(body, testContent)
t.end()
})
})

tape('transparently supports gzip decoding to pipes', function(t) {
var options = { url: server.url + '/foo', gzip: true }
var chunks = []
Expand Down

0 comments on commit 25dfff5

Please sign in to comment.