From 2a2dffa8828cc4fa3d45f480dd40d0a790c132c3 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 12 Jun 2023 21:04:10 +0200 Subject: [PATCH] Backport v3 handling of chunked transfer end The v2 backport produced invalid errors in some situations. --- src/index.js | 51 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/src/index.js b/src/index.js index 9013f1d..3cb9d41 100644 --- a/src/index.js +++ b/src/index.js @@ -352,26 +352,47 @@ export default function fetch(url, opts) { }; function fixResponseChunkedTransferBadEnding(request, errorCallback) { - let socket; + const LAST_CHUNK = Buffer.from('0\r\n\r\n'); - request.on('socket', s => { - socket = s; - }); + let isChunkedTransfer = false; + let properLastChunkReceived = false; + let previousChunk; request.on('response', response => { const {headers} = response; - if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) { - response.once('close', hadError => { - // if a data listener is still present we didn't end cleanly - const hasDataListener = socket.listenerCount('data') > 0; + isChunkedTransfer = headers['transfer-encoding'] === 'chunked' && !headers['content-length']; + }); - if (hasDataListener && !hadError) { - const err = new Error('Premature close'); - err.code = 'ERR_STREAM_PREMATURE_CLOSE'; - errorCallback(err); - } - }); - } + request.on('socket', socket => { + const onSocketClose = () => { + if (isChunkedTransfer && !properLastChunkReceived) { + const error = new Error('Premature close'); + error.code = 'ERR_STREAM_PREMATURE_CLOSE'; + errorCallback(error); + } + }; + + const onData = buf => { + properLastChunkReceived = Buffer.compare(buf.slice(-5), LAST_CHUNK) === 0; + + // Sometimes final 0-length chunk and end of message code are in separate packets + if (!properLastChunkReceived && previousChunk) { + properLastChunkReceived = ( + Buffer.compare(previousChunk.slice(-3), LAST_CHUNK.slice(0, 3)) === 0 && + Buffer.compare(buf.slice(-2), LAST_CHUNK.slice(3)) === 0 + ); + } + + previousChunk = buf; + }; + + socket.prependListener('close', onSocketClose); + socket.on('data', onData); + + request.on('close', () => { + socket.removeListener('close', onSocketClose); + socket.removeListener('data', onData); + }); }); }