Skip to content

Request: read method and headers from a non-native input when init does not set them - #42761

Closed
robobun wants to merge 1 commit into
mainfrom
robobun/788b12ec/request-init-method-fallthrough
Closed

robobun wants to merge 1 commit into
mainfrom
robobun/788b12ec/request-init-method-fallthrough

Conversation

@robobun

@robobun robobun commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • new Request(input, init) returns a GET when input is not a native Request and init has no method. new Request({ url, method: "POST", body }, {}) gives method === "GET" with the body still attached. A Request look-alike (a prototype chain that reaches Request.prototype, as srvx builds) fails the same way. Without an init the method is kept.
  • The constructor loop in src/runtime/webcore/Request.rs:1080 walks init before input. Init::init (src/runtime/webcore/Response.rs:1230) returns method: GET for an object with no method property. The explicit_check guard at line 1082 only made init probe for an own method or headers property when input was a native Request (DOMWrapper). For any other input the default GET was stored and input.method was never read.

Fix

  • explicit_check is now true for the init object whenever two values are walked. It no longer depends on the type of input. So init only claims method and headers when it has that property. Otherwise the loop falls through to input, native or not.
  • This is the rule the constructor already used for a native Request input. The change makes a plain object and a look-alike follow the same rule. Bun accepts object inputs on purpose (test "Creating Request from existing Request #874"), so a Node-style brand check is not the fix.
  • Verified: test/js/web/fetch/fetch.test.ts (new describe("new Request(input, init) with a non-native input"), two tests, both fail on the released binary). Also fetch-args.test.ts, body-clone.test.ts, body.test.ts, headers-case.test.ts, body-mixin-errors.test.ts, request-cyclic-reference.test.ts, bun-server.test.ts.

Background

  • Request::constructor merges up to two JS values into one Request: the init dictionary and the input object. A Fields bitset records which fields are set. Each value only fills fields that are still unset.
  • Init::init is shared with Response. It parses headers, status, statusText and method from an object. It has no "absent" state for method: the default is GET.
  • A DOMWrapper is a JS cell backed by a native class. as_direct::<Request>() on it returns the native payload, which is the fast path for new Request(nativeRequest, init).
Notes

Repro (released 1.4.3 and main):

const d = new Request({ url: "http://localhost/a", method: "POST", body: "{}" }, {});
console.log(d.method); // GET before, POST after

Headers followed the same pattern. Init::init returns headers: None for {} so they happened to survive, but the old guard still read input.headers only for a native input. With the fix new Request(input, { headers }) replaces the input headers (as with a native input) and new Request(input, {}) keeps them.

Node 26 throws a TypeError for a look-alike input (brand check). Bun keeps its object-input path, so the fix is the consistent merge, not a throw.

Failures seen in the full fetch.test.ts run are environmental: public network (simultaneous HTTPS fetch, tlsextname), root permissions (bad permissions throws), IPv6 on the container, and GC timeouts under the ASAN debug build. None touch the constructor.


no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/web/fetch/fetch.test.ts

…es not set them

new Request(input, init) walked init before input. For a non-native input
(a plain object or a Request look-alike) the init object claimed the method
field with the default GET even when it had no method property, so the
input's method was never read. The same happened for headers. Only a native
Request input was probed for an explicit property. Apply that probe to every
init object so the missing fields fall through to the input.

Fixes #42759
@coderabbitai

coderabbitai Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: bdc24ea8-59f1-41ba-9c66-f5a02ba5acb7

📥 Commits

Reviewing files that changed from the base of the PR and between dad5177 and f94f720.

📒 Files selected for processing (2)
  • src/runtime/webcore/Request.rs
  • test/js/web/fetch/fetch.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.


Walkthrough

Request::construct_into now identifies the explicit initialization object by argument position and identity. Regression tests cover method preservation and override, header replacement, and body copying for request-like and plain-object inputs.

Changes

Request initialization

Layer / File(s) Summary
Construction and regression tests
src/runtime/webcore/Request.rs, test/js/web/fetch/fetch.test.ts
Request::construct_into applies method and header checks to the first value in a two-value input sequence. Tests cover request-like and plain-object inputs, including method, headers, and body behavior.

Suggested reviewers: jarred-sumner

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to f94f7

The Request initialization change preserves the intended fallback and override behavior, with no current merge-blocking risk identified.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change: reading method and headers from non-native inputs when init does not set them.
Description check ✅ Passed The description explains the problem, fix, background, and verification details. It does not use the template headings exactly, but it provides the required information and is mostly complete.

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

The review raised no concerns. No open review threads remain. Waiting on CI.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — small, well-scoped fix that makes the init/input merge rule consistent regardless of input's JSType.

What was reviewed:

  • Traced values_to_try construction: when len == 2, [0] is always init and [1] is always input, so value == values_to_try[0] correctly identifies the init iteration; the same identity comparison is already used at line 1252 for the url fallback.
  • Checked the second iteration and the len == 1 cases (URL-only input, object-only input) — behavior is unchanged; the DOMWrapper fast path still sets Method/Headers before the Init::init block is reached, so a native init is unaffected.
  • Verified the other per-field reads (body, url, signal, redirect, cache, mode) already use fast_get/get_optional_enum which return None for absent, so method/headers via Init::init were the only fields with this defaulting bug.
  • Tests cover the matrix: RequestLike vs plain object, init absent/empty/overriding, method and headers, and header replacement semantics.
Extended reasoning...

Overview

The PR changes one line in Request::from_init (src/runtime/webcore/Request.rs) that computes explicit_check, the guard that makes the constructor's merge loop probe init for an actual method/headers property before letting Init::init's default (GET) claim the field. Previously the guard only fired when init was a FinalObject and input was a native DOMWrapper; now it fires whenever two values are being merged and we are on the first (init) iteration. A new describe block in test/js/web/fetch/fetch.test.ts exercises both a Request-prototype-forwarding look-alike and a plain { url, method, headers, body } input against several init shapes.

Security risks

None. This is option-merging logic in the Request constructor; no parsing of untrusted bytes, no auth/crypto/permissions, no allocation-size or path handling changes. The only new observable calls are the existing fast_get property probes on init, now reached in more cases — same exception-propagation pattern as before.

Level of scrutiny

Low-to-moderate. The diff is a three-line predicate rewrite plus a two-line comment. I walked the values_to_try_ array construction to confirm index 0 is always init when length is 2, and confirmed the JSValue == JSValue identity comparison is already the idiom this function uses (line 1252) to tell which iteration it is on. I checked each other branch that reads explicit_check (header_check, method_check) and each path where explicit_check flips from false→true under the new rule (Proxy init, DOMWrapper init, non-native input) — in every case the new behavior is either identical (fields already set via the DOMWrapper fast path) or strictly more correct (falls through to input instead of pinning GET). The degenerate new Request(obj, obj) case where init === input still resolves sensibly since both iterations read the same object.

Other factors

Tests are added to the existing fetch.test.ts alongside the related #874 object-input test, use the one-line issue-link comment style, and cover the variant matrix REVIEW.md asks for (init present/absent, override vs inherit, both field kinds, both non-native input shapes). The PR notes the tests fail on the released binary. Bun intentionally diverges from the WHATWG brand check here (per the pre-existing #874 test), so matching Node's TypeError is not the goal — making the accepted-object path internally consistent is, and this does that.

@robobun

robobun commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. No changes needed. Buildkite build #115699 is still in progress.

@robobun

robobun commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 5:21 PM PT - Sep 14th, 2026

✅ @robobun, your commit f94f7201a19d2235f93fb3533964c16bb04ae453 passed in Build #115699! 🎉


🧪   To try this PR locally:

bunx bun-pr 42761

That installs a local version of the PR into your bun-42761 executable, so you can run:

bun-42761 --bun

@robobun

robobun commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of #41994. It fixes the same constructor loop for every kind of init and input, and it now carries this PR's tests for #42759. Reopen this PR if the smaller change is preferred.

@robobun robobun closed this Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant