@@ -286,50 +286,94 @@ export function getH2HeadersAfterModification(
286286 } ;
287287}
288288
289- // Helper to handle content-length nicely for you when rewriting requests with callbacks
290- export function getContentLengthAfterModification (
289+ // When modifying requests, we ensure you always have correct framing, as it's impossible
290+ // to send a request with framing that doesn't match the body.
291+ export function getRequestContentLengthAfterModification (
291292 body : string | Uint8Array | Buffer ,
292293 originalHeaders : Headers | RawHeaders ,
293294 replacementHeaders : Headers | RawHeaders | undefined ,
294- mismatchAllowed : boolean = false
295+ context : {
296+ httpVersion : 1 | 2
297+ // N.b. we ignore the method though - you can proxy requests that include a body
298+ // even if they really shouldn't, as long as it's plausibly parseable.
299+ }
295300) : string | undefined {
296301 // If there was a content-length header, it might now be wrong, and it's annoying
297302 // to need to set your own content-length override when you just want to change
298- // the body. To help out, if you override the body but don't explicitly override
299- // the (now invalid) content-length, then we fix it for you.
303+ // the body. To help out, if you override the body in a way that results in invalid
304+ // content-length headers, we fix them for you.
305+
306+ // For HTTP/2, framing is optional/advisory so we can just skip this entirely.
307+ if ( context . httpVersion !== 1 ) return undefined ;
308+
309+ const resultingHeaders = replacementHeaders || originalHeaders ;
300310
301- if ( getHeaderValue ( originalHeaders , 'content-length' ) === undefined ) {
302- // Nothing to override - use the replacement value, or undefined
303- return getHeaderValue ( replacementHeaders || { } , 'content-length' ) ;
311+ if ( getHeaderValue ( resultingHeaders , 'transfer-encoding' ) ?. includes ( 'chunked' ) ) {
312+ return undefined ; // No content-length header games needed
304313 }
305314
306- if ( ! replacementHeaders ) {
307- // There was a length set, and you've provided a body but not changed it.
308- // You probably just want to send this body and have it work correctly,
309- // so we should fix the content length for you automatically.
310- return byteLength ( body ) . toString ( ) ;
315+ const expectedLength = byteLength ( body ) . toString ( ) ;
316+ const contentLengthHeader = getHeaderValue ( resultingHeaders , 'content-length' ) ;
317+
318+ if ( contentLengthHeader === expectedLength ) return undefined ;
319+ if ( contentLengthHeader === undefined ) return expectedLength ; // Differs from responses
320+
321+ // The content-length is expected, but it's wrong or missing.
322+
323+ // If there is a wrong content-length set, and it's not just leftover from the original headers (i.e.
324+ // you intentionally set it) then we show a warning since we're ignoring your (invalid) instructions.
325+ if ( contentLengthHeader && contentLengthHeader !== getHeaderValue ( originalHeaders , 'content-length' ) ) {
326+ console . warn ( `Invalid request content-length header was ignored - resetting from ${
327+ contentLengthHeader
328+ } to ${
329+ expectedLength
330+ } `) ;
311331 }
312332
313- // There was a content length before, and you're replacing the headers entirely
314- const lengthOverride = getHeaderValue ( replacementHeaders , 'content-length' ) ?. toString ( ) ;
333+ return expectedLength ;
334+ }
315335
316- // If you're setting the content-length to the same as the origin headers, even
317- // though that's the wrong value, it *might* be that you're just extending the
318- // existing headers, and you're doing this by accident (we can't tell for sure).
319- // We use invalid content-length as instructed, but print a warning just in case.
320- if (
321- lengthOverride === getHeaderValue ( originalHeaders , 'content-length' ) &&
322- lengthOverride !== byteLength ( body ) . toString ( ) &&
323- ! mismatchAllowed // Set for HEAD responses
324- ) {
325- console . warn ( oneLine `
326- Passthrough modifications overrode the body and the content-length header
327- with mismatched values, which may be a mistake. The body contains
328- ${ byteLength ( body ) } bytes, whilst the header was set to ${ lengthOverride } .
329- ` ) ;
336+ // When modifying responses, we ensure you always have correct framing, but in a slightly more
337+ // relaxed way than for requests: we allow no framing and HEAD responses, we just block invalid values.
338+ export function getResponseContentLengthAfterModification (
339+ body : string | Uint8Array | Buffer ,
340+ originalHeaders : Headers | RawHeaders ,
341+ replacementHeaders : Headers | RawHeaders | undefined ,
342+ context : {
343+ httpMethod : string
344+ httpVersion : 1 | 2
345+ }
346+ ) : string | undefined {
347+ // For HEAD requests etc, you can set an arbitrary content-length header regardless
348+ // of the empty body, so we don't bother checking anything. For HTTP/2, framing is
349+ // optional/advisory so we can just skip this entirely.
350+ if ( context . httpVersion !== 1 || context . httpMethod === 'HEAD' ) return undefined ;
351+
352+ const resultingHeaders = replacementHeaders || originalHeaders ;
353+
354+ if ( getHeaderValue ( resultingHeaders , 'transfer-encoding' ) ?. includes ( 'chunked' ) ) {
355+ return undefined ; // No content-length header games needed
356+ }
357+
358+ const expectedLength = byteLength ( body ) . toString ( ) ;
359+ const contentLengthHeader = getHeaderValue ( resultingHeaders , 'content-length' ) ;
360+
361+ if ( contentLengthHeader === expectedLength ) return undefined ;
362+ if ( contentLengthHeader === undefined ) return undefined ; // Differs from requests - we do allow this for responses
363+
364+ // The content-length is set, but it's wrong.
365+
366+ // If there is a wrong content-length set, and it's not just leftover from the original headers (i.e.
367+ // you intentionally set it) then we show a warning since we're ignoring your (invalid) instructions.
368+ if ( contentLengthHeader && contentLengthHeader !== getHeaderValue ( originalHeaders , 'content-length' ) ) {
369+ console . warn ( `Invalid response content-length header was ignored - resetting from ${
370+ contentLengthHeader
371+ } to ${
372+ expectedLength
373+ } `) ;
330374 }
331375
332- return lengthOverride ;
376+ return expectedLength ;
333377}
334378
335379// Function to check if we should skip https errors for the current hostname and port,
0 commit comments