Request constructor uses wrong body when given another request as input and options with a body
Categories
(Core :: DOM: Networking, defect, P2)
Tracking
()
Tracking | Status | |
---|---|---|
firefox124 | --- | fixed |
People
(Reporter: jarens, Assigned: dotoole)
References
(Blocks 1 open bug)
Details
(Whiteboard: [necko-triaged][necko-priority-queue], [wptsync upstream])
Attachments
(1 file)
When creating a new Request
in JavaScript, an input
and options
may be passed as arguments to the Request
's constructor. If input
is another Request
instance where its body
has been consumed (disrupted or locked), and options
contains a body
property, the new Request
instance will use the body from input
, and not from options
, causing an an error to be thrown because the input
request's body can no longer be used.
The fetch request class spec, starting at step 34 under "The new Request(input, init) constructor steps are: " defines the steps that should be taken regarding body
when constructing a new Request
. In particular, step 38 essentially says that if init
has a body
and it's non-null then it should take precedence over input
.
In Request.cpp starting at line 280 it looks like the constructor checks if aInput
is a request instance, then gets and uses the body
from it without first checking if aInit
has a body
.
Chrome, Safari, and Node.js >= 18 all seem to conform to the spec, whereas Firefox does not.
Reproduction steps:
async function test() {
const req1 = new Request('/', {
body: 'req1',
method: 'POST',
});
await req1.text(); // Consume the Request's body.
// The `req2` body passed in options should take precedence, and the
// `Request` constructor should not throw an error.
const req2 = new Request(req1, { body: 'req2' });
}
test()
.then(() => { console.log('test completed with no errors'); })
.catch((err) => { console.error('test failed:', err.message); });
For context, my use case is that I'd like to "reuse" a request after it's been fetched. For example, retrying a request with a refreshed CSRF token header when the first request receives a 401 response. Constructing a new request instance out of the original with the initial body passed in through the constructor's options
would be the best option because all of the original request's properties would be passed along to the new request. This is not trivial to do in JavaScript because Request
is not enumerable, and properties like headers
have interior mutability, which would need to be tracked or manually copied from the original request into the new request's options
.
Use case:
// This is somewhat simplified from what I'm trying to do in the real
// world, but this is the basic idea.
const body = JSON.stringify({ foo: 'bar' });
let request = new Request('/', {
body,
headers: {
'x-csrf-token': 'expired token value',
},
method: 'POST',
});
let response = await fetch(request);
if (response.status === 401) {
request = new Request(request, { body });
request.headers.set('x-csrf-token', 'refreshed token value');
response = await fetch(request);
}
Comment 1•1 year ago
|
||
Thank you for the great bug report. This seems straightforward to fix.
Comment 6•1 year ago
|
||
bugherder |
Description
•