-
Notifications
You must be signed in to change notification settings - Fork 567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
When Content-Length is specified, fetch() raises RequestContentLengthMismatchError on redirection #2021
Comments
Thanks for reporting! Can you provide steps to reproduce? We often need a reproducible example, e.g. some code that allows someone else to recreate your problem by just copying and pasting it. If it involves more than a couple of different file, create a new repository on GitHub and add a link to that. In this case we need both the server and the client. A PR with a fix would also be highly appreciated! |
@KhafraDev did we or the fetch spec miss a step in the redirection logic? |
No, the |
@mcollina The code to reproduce the issue is already in the ‘Reproducible By’ section. |
What does the spec say? Do we need to filter or throw an error? |
The RFC mentioned in the OP recommends removing some headers on redirect, that's likely what we should do. |
@annevk Do you have any guidance here? |
As I said in #2021 (comment), we need both the client and server. |
Here's a small repro import { once } from 'events'
import { createServer } from 'http'
import { fetch } from './index.js'
const server = createServer((req, res) => {
if (req.url === '/redirect') {
res.writeHead(302, { Location: '/redirect2' })
res.end()
return
}
console.log('hello', req.url)
res.end()
}).listen(0)
await once(server, 'listening')
await fetch(`http://localhost:${server.address().port}/redirect`, {
method: 'POST',
body: 'a+b+c',
headers: {
'content-length': Buffer.byteLength('a+b+c')
}
}) |
The "request" guarded header validation should fail with content-length and therefore it should just be thrown away as far as I can see? |
Why is that? |
I don't think our code any longer corresponds with https://fetch.spec.whatwg.org/#headers-class. Maybe time to have another iteration? |
The headers are being removed in fetch, this is a bug elsewhere: Lines 1177 to 1195 in e6fc80f
where requestBodyHeader is const requestBodyHeader = [
'content-encoding',
'content-language',
'content-location',
'content-type'
] |
Oh nvm, adding |
But that list should not include it:
|
I think it's in the header append guard logic where it should be removed. |
It's not powerful enough in the server; it disallows setting a bunch of headers. We purposefully deviate from the spec to match deno/other server environments.
I'm assuming that this is because normally it's already removed in the Headers impl. I think it's fair to add it to that list and add a comment about why it's there (along with a comment linking to the spec). |
I've purposefully kept it close to Rafael's version, as it makes headers faster by tenfold (using a map instead of an array). Unfortunately that decision snowballed to where the implementation is completely different from what the spec says to do. |
Rather than allow everything I think it's better to deviate on specific header names where it makes sens. |
I have mixed feelings on this... Not sure what to think. |
I have a branch that makes it spec compliant (see: https://github.com/KhafraDev/undici/tree/change-headerslist-to-array), but after benchmarking, the performance was so bad that I decided against it. I actually published that branch for this exact reason - to show how badly it performs compared to our current impl.
I don't think we should filter any headers in fetch. Adding content-length to requestBodyHeader fixes the issue in a spec-compliant & rfc compliant way. |
https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch defines how to set |
Yes, currently the list is (https://github.com/nodejs/undici/blob/v5.21.0/lib/fetch/constants.js#L51-L56): const requestBodyHeader = [
'content-encoding',
'content-language',
'content-location',
'content-type'
] To be RFC 9110 compliant, we should remove the following header fields (https://www.rfc-editor.org/rfc/rfc9110#section-15.4-6.5.1):
So: const requestBodyHeader = [
'content-encoding',
'content-language',
'content-location',
'content-type',
+ 'content-length',
+ 'digest',
+ 'last-modified'
] |
digest isn't a filtered header name and last-modified is only a CORS safelisted header (not something undici implements) |
Thank you for solving this issue guys. |
Bug Description
When the
Content-Length
request header field is manually specified (instead of automatically generated),fetch()
raises aRequestContentLengthMismatchError
in case of redirection.Reproducible By
Send a
POST
request to the resource identified by the/sirene/public/recherche
target URI to search for a non-existing company whose namefoobarbaz
is provided in the request body:The resource replies with a
302
response with aLocation: /sirene/error/autre.action
header field. Sofetch
automatically (by default) redirects the original request to the target URI/sirene/error/autre.action
and raises aRequestContentLengthMismatchError
:Expected Behavior
The specified
Content-Length
request header field is correct in the original request so I don’t expect aRequestContentLengthMismatchError
after automatic redirection.Logs & Screenshots
I assume that during redirection,
fetch()
resends the original request but with the following modifications:/sirene/public/recherche
to/sirene/error/autre.action
;POST
request method is changed toGET
;But it doesn’t remove the content-specific header fields (
Content-Type
andContent-Length
here), contrary to what the latest HTTP specification RFC 9110 recommends:The non-compliance of Undici to point 5 likely causes the
RequestContentLengthMismatchError
. Indeed, sending aGET
request without a body but with aContent-Length
header field raises the sameRequestContentLengthMismatchError
:Output:
Environment
MacOS 11.6.7, Node 19.7.0.
The text was updated successfully, but these errors were encountered: