Backport v3 handling of chunked transfer end

The v2 backport produced invalid errors in some situations.
This commit is contained in:
Christian Kamm 2023-06-12 21:04:10 +02:00
parent afb36f6c17
commit 2a2dffa882
1 changed files with 36 additions and 15 deletions

View File

@ -352,26 +352,47 @@ export default function fetch(url, opts) {
}; };
function fixResponseChunkedTransferBadEnding(request, errorCallback) { function fixResponseChunkedTransferBadEnding(request, errorCallback) {
let socket; const LAST_CHUNK = Buffer.from('0\r\n\r\n');
request.on('socket', s => { let isChunkedTransfer = false;
socket = s; let properLastChunkReceived = false;
}); let previousChunk;
request.on('response', response => { request.on('response', response => {
const {headers} = response; const {headers} = response;
if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) { isChunkedTransfer = 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;
if (hasDataListener && !hadError) { request.on('socket', socket => {
const err = new Error('Premature close'); const onSocketClose = () => {
err.code = 'ERR_STREAM_PREMATURE_CLOSE'; if (isChunkedTransfer && !properLastChunkReceived) {
errorCallback(err); 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);
});
}); });
} }