Skip to content

node:url: honor the {windows} option in fileURLToPath and pathToFileURL - #35260

Closed
robobun wants to merge 3 commits into
mainfrom
claude/farm-7a7ae7f8-url-windows-option
Closed

robobun wants to merge 3 commits into
mainfrom
claude/farm-7a7ae7f8-url-windows-option

Conversation

@robobun

@robobun robobun commented Jul 23, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Node.js >= 22.1 documents an options.windows flag on both url.fileURLToPath(url, options) and url.pathToFileURL(path, options) that forces Windows or POSIX path semantics regardless of the host OS. Cross-platform tooling relies on this to handle Windows file URLs on Linux CI and vice versa. Bun silently ignored the option and always applied host-OS semantics.

import * as url from "node:url";
// on Linux:
url.fileURLToPath("file:///C:/x", {windows: true})
// node "C:\\x"        bun "/C:/x"
url.fileURLToPath("file://host/s/x", {windows: true})
// node "\\\\host\\s\\x"   bun throws ERR_INVALID_FILE_URL_HOST
url.pathToFileURL("C:\\x", {windows: true}).href
// node "file:///C:/x"   bun "file:///<cwd>/C:/x"
url.pathToFileURL("\\\\srv\\s\\x", {windows: true}).href
// node "file://srv/s/x"  bun "file:///<cwd>/srv/s/x"

Cause

src/js/node/url.ts exported Bun.fileURLToPath and Bun.pathToFileURL directly. Those natives select POSIX or Windows behaviour via #if OS(WINDOWS) at compile time and accept no options argument, so the second argument was dropped on the floor. (Notably the sibling fileURLToPathBuffer, implemented in JS, already handles options.windows correctly.)

Fix

Wrap both exports in JS. When options.windows is unset or matches the host platform, fall through to the native (no behaviour or performance change for the common case). When it overrides the host, apply Node's getPathFromURLWin32 / getPathFromURLPosix and the UNC-aware pathToFileURL encoder ported from lib/internal/url.js.

Verification

New tests in test/js/node/url/url-fileurltopath.test.js and test/js/node/url/url-pathtofileurl.test.js exercise {windows: true} and {windows: false} on both Linux and Windows, including drive-letter paths, UNC paths, percent-encoding validation and round-tripping. Both fail against USE_SYSTEM_BUN=1 and pass with this change on Linux x64 and Windows x64.


no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/node/url/url-fileurltopath.test.js test/js/node/url/url-pathtofileurl.test.js

robobun added 2 commits July 23, 2026 09:36
Both node:url exports were bound directly to the Bun.fileURLToPath and
Bun.pathToFileURL natives, which pick POSIX or Windows path semantics at
compile time and take no options argument. Node >= 22.1 documents
options.windows on both functions to force the other platform's semantics,
which cross-platform tooling relies on to handle Windows file URLs on
POSIX CI and vice versa.

Wrap the natives so the host-platform fast path is unchanged, and port
Node's getPathFromURLWin32/getPathFromURLPosix and the UNC-aware
pathToFileURL encoder for the override case.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 8 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e697c88b-0204-470f-85b0-af57c826ed54

📥 Commits

Reviewing files that changed from the base of the PR and between 892b1da and 9f6e1a1.

📒 Files selected for processing (3)
  • src/js/node/url.ts
  • test/js/node/url/url-fileurltopath.test.js
  • test/js/node/url/url-pathtofileurl.test.js

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

@robobun

robobun commented Jul 23, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

Tests pass locally on Linux x64 and Windows x64 (fail-before verified on both). Review feedback on forward-slash UNC handling addressed in 9f6e1a1.

CI build 78615: 194 jobs passed, 0 failed. The remaining 2 jobs are :darwin: 14 aarch64 - test-bun stuck in the scheduler queue for ~3h (infra backlog, not a test failure). All test-level failures in the build are unrelated flakes that passed on retry (fs FileHandle GC, repl EPIPE, no-orphans darwin timeout, next.js dev-server, etc.); url-fileurltopath.test.js and url-pathtofileurl.test.js passed on every lane.

Related: #33373 also adds options.windows to Bun.fileURLToPath on the C++ side as part of percent-decoding validation, but does not touch pathToFileURL. This PR keeps the natives unchanged and handles the option in the node:url JS wrapper for both functions.

@robobun

robobun commented Jul 23, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 7:29 AM PT - Jul 23rd, 2026

✅ @robobun, your commit 9f6e1a1277a0118adca3b17aa7cd973b60c09c35 passed in Build #78615! 🎉


🧪   To try this PR locally:

bunx bun-pr 35260

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

bun-35260 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 4 issues this PR may fix:

  1. pathToFileURL mishandles Windows UNC extended-length paths on Linux #28622 - PR adds pure-JS pathToFileURL with {windows: true} that correctly handles UNC extended-length paths on Linux
  2. pathToFileURL on Windows fails to include drive letter when input path starts with / #14816 - PR's Windows-aware pathToFileURL wrapper resolves drive letters for POSIX-style absolute paths on Windows
  3. Inconsistent validation of percent-encoded file URLs compared to Node.js  #29174 - PR's JS fileURLToPath implementation adds proper percent-decoding validation that surfaces URIError for malformed URLs
  4. SolidStart 1.1.0 does not work with Bun #18748 - Same root cause as pathToFileURL on Windows fails to include drive letter when input path starts with / #14816; SolidStart fails because pathToFileURL("/@react-refresh") on Windows omits the drive letter

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #28622
Fixes #14816
Fixes #29174
Fixes #18748

🤖 Generated with Claude Code

@robobun

robobun commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

On the bot's suggested issues: none of #28622, #14816, #29174 or #18748 are fixed here. Those all concern host-platform behaviour with no options argument, which this PR deliberately leaves on the existing native fast path. #28622 is being handled in #28623 and #29174 in #33373; #14816/#18748 are a separate Windows path-resolve issue in the native. This PR only adds the documented {windows} override.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. url: validate percent-decoding and platform path rules in fileURLToPath #33373 - Also implements options.windows support in fileURLToPath (via native binding approach rather than JS wrapper), with overlapping getPathFromURLWin32/getPathFromURLPosix logic

🤖 Generated with Claude Code

Comment thread src/js/node/url.ts Outdated
path.win32.resolve normalizes forward-slash UNC roots (//srv/share)
to \\srv\share, so the UNC branch must also fire when the resolved
path starts with \\, not just when the raw input does. Matches Node's
lib/internal/url.js.

@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.

The forward-slash UNC issue from my earlier review is fixed in 9f6e1a1 and now matches Node's control flow; I didn't find further problems in the override path. Deferring to a human mainly because this overlaps with #33373 (native-side options.windows for fileURLToPath) and someone should decide which approach lands, and because ~160 lines of freshly ported lib/internal/url.js logic is more than I want to auto-approve.

What was reviewed:

  • getPathFromURLWin32/getPathFromURLPosix against Node's reference — drive-letter check, encoded-separator rejection, UNC hostname handling all match.
  • pathToFileURL UNC branch now operates on resolved (extended-UNC prefix, empty-servername check at index 2, hostname assignment) — matches Node.
  • encodePathChars table covers the chars Node encodes explicitly; remaining C0/non-ASCII are handled by new URL()'s path parser, so the file://-prefixed-string approach is equivalent to Node's outURL.pathname = ....
  • Host-platform fast path (!windows === !isWindows) is unchanged, so no regression risk for callers that don't pass options.
Extended reasoning...

Overview

Adds options.windows support to node:url's fileURLToPath and pathToFileURL by wrapping the existing Bun.fileURLToPath/Bun.pathToFileURL natives. When the option is unset or matches the host OS, behaviour is byte-identical to before (native fast path). When it overrides the host, ~160 lines of logic ported from Node's lib/internal/url.js (getPathFromURLWin32, getPathFromURLPosix, UNC-aware pathToFileURL with an encodePathChars helper) run instead. Two test files gain ~75 lines covering both override directions on both host platforms.

Security risks

None identified. This is pure path-string ↔ URL-string conversion with no filesystem, network, or privilege effects. Inputs are validated (validateString, isURL) before use; error paths use the centralized $ERR_* machinery. The urlPathEncodings lookup table uses __proto__: null.

Level of scrutiny

Medium. This is a Node-compat addition to a widely-used module (node:url), but the change is additive: existing callers that don't pass {windows} hit the unchanged native path, so regression surface is limited to the new option. The ported code is a close transcription of Node's reference and my earlier review already caught and fixed the one control-flow divergence (forward-slash UNC re-check after resolve). Tests cover drive letters, UNC, extended-UNC, percent-encoding validation, and round-tripping.

Other factors

  • Overlap with #33373: that PR also implements options.windows for fileURLToPath, but on the C++ side. The author has acknowledged this in the thread. A maintainer should decide whether to land the JS-wrapper approach here (which also covers pathToFileURL), the native approach in #33373, or reconcile the two. This is the main reason I'm not auto-approving.
  • Prior review feedback addressed: my earlier finding on forward-slash UNC handling was fixed in 9f6e1a1 with a regression test added.
  • Test gating: some {windows: true} UNC assertions are guarded if (!isWindows) because on a Windows host they route through the native Bun.pathToFileURL, whose extended-UNC handling is a pre-existing gap already tracked by test.todo("UNC paths") in the same file. The comment explains this honestly rather than silently skipping.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing: the options.windows handling this PR adds landed on main in #34660 (43caaf3), which replaced the node:url exports with a port of Node's pathToFileURL and fileURLToPath (src/js/node/url.ts: pathToFileURL(filepath, options) resolves with path.win32/path.posix and handles UNC paths, fileURLToPath(url, options) dispatches to getPathFromURLWin32/getPathFromURLPosix when the option is set). This branch now conflicts with that rewrite.

Verified against an unmodified main (b7a0431) debug build on Linux x64:

  • This PR's test changes to test/js/node/url/url-fileurltopath.test.js and test/js/node/url/url-pathtofileurl.test.js, applied on top of main, pass (6 pass, 0 fail, plus the 2 pre-existing todos).
  • The four examples from the PR description return Node's results: "C:\\x", "\\\\host\\s\\x", file:///C:/x, file://srv/s/x.
  • A wider set of {windows: true} / {windows: false} probes (drive letters, UNC and \\?\UNC\ paths, IDN hosts, encoded / and \, the ERR_INVALID_* cases, options of null/undefined) produces output identical to Node v26.3.0.
  • main also carries Node's own test/js/node/test/parallel/test-url-pathtofileurl.js and test-url-fileurltopath.js, which exercise the option and pass.

@robobun robobun closed this Aug 13, 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.

2 participants