Skip to content

fix(server): read a file:// folder link on Windows - #817

Merged
Ishaan Gangwani (ishaan1124) merged 3 commits into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/folder-resolve-windows-path
Sep 29, 2026
Merged

Ishaan Gangwani (ishaan1124) merged 3 commits into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/folder-resolve-windows-path

Conversation

@aniruddhaadak80

Copy link
Copy Markdown
Contributor

What

The folder-validate route expands the client's path like this:

const withoutFileUrl = raw.startsWith("file://") ? decodeURIComponent(new URL(raw).pathname) : raw
...
return path.resolve(withoutFileUrl)

URL.pathname keeps the leading slash on a drive URL, and decodeURIComponent throws on a malformed escape.

Why it matters

Measured on this Windows machine with the original expression:

"file:///C:/Users/me/Desktop"  -> "C:\\C:\\Users\\me\\Desktop"    doubled drive
"file:///home/me/data"         -> "C:\\home\\me\\data"            POSIX path treated as relative
"C:/Users/me/Desktop"          -> "C:\\Users\\me\\Desktop"        correct (plain path)
"file:///C:/work/100%25"       -> "C:\\C:\\work\\100%"            doubled drive, decoded
"file:///C:/work/a%zz"         -> THROWS URIError: URI error

path.resolve("/C:/Users/me/Desktop") prepends the current drive to a string that already has one, and the route then answers path not found for a folder that is really there.

A plain path works and a file:// link does not, which is the confusing part: the same folder is selectable one way and not the other. This is the route behind "choose a folder", so it is exactly the path a user takes when the browser hands over a file:// link.

The POSIX line is the same bug mirrored: on a POSIX host file:///home/me/data is already correct, so this only ever surfaced on Windows.

And a stray % doesn't produce a "no such folder" answer — nothing catches URIError, so the request fails outright.

Verification

expandPath was module-private with no test, so this exports it and adds test/server/folder-resolve-path.test.ts. Four of five cases fail before the fix:

(fail) a Windows file URL keeps its drive letter exactly once
(fail) a percent-escaped file URL decodes to the real path
(fail) a file URL with a stray percent does not throw
(fail) a POSIX file URL resolves normally
(pass) a plain path and a tilde path are unchanged
 1 pass  4 fail
(pass) a Windows file URL keeps its drive letter exactly once
(pass) a percent-escaped file URL decodes to the real path
(pass) a file URL with a stray percent does not throw
(pass) a POSIX file URL resolves normally
(pass) a plain path and a tilde path are unchanged
 5 pass
 0 fail

The POSIX case is asserted against path.resolve rather than a literal, so it stays correct on POSIX hosts too. The stray-percent case asserts not.toThrow instead of an exact string, since the point is that a bad escape degrades to a path that does not exist rather than a failed request.

The neighbouring test/server/project-folders.test.ts is 4 pass / 3 fail both before and after — identical to main in this checkout. (At the default 15 s timeout those tests take 20-40 s and the result flaps; at a realistic timeout it is stable and identical to baseline. The 3 baseline failures are Windows symlink/EPERM cases.)

The change

function fromFileUrl(raw: string): string {
  const pathname = new URL(raw).pathname
  // Each escape is decoded on its own, so one malformed percent cannot discard
  // the rest of a path that is otherwise fine.
  const decoded = pathname.replace(/%[0-9a-f]{2}/gi, (escape) => {
    try {
      return decodeURIComponent(escape)
    } catch {
      return escape
    }
  })
  // "/C:/rest" is the URL spelling of "C:\rest".
  return /^\/[a-z]:[\\/]/i.test(decoded) ? decoded.slice(1) : decoded
}

Bun 1.3.14 has no URL.filePath — verified, it is undefined — so the drive slash is stripped explicitly rather than relying on a platform accessor.

On the generated SDK

AGENTS.md asks for ./tooling/repo/generate.ts after a src/server change, so it was run. It rewrote 31 files, but git diff --stat reports 0 lines for openapi.json and the diff is otherwise empty — line endings only. No route, parameter, or response field changed, so the generated client is unchanged and that churn was reverted.

bun run typecheck clean; touched files are Prettier-clean (checked on LF-normalized copies — this checkout has core.autocrlf=true, which makes Prettier flag every file in the repo).

Fixes #816

@vercel

vercel Bot commented Sep 28, 2026

Copy link
Copy Markdown

ANIRUDDHA ADAK (@aniruddhaadak80) is attempting to deploy a commit to the InkVell Team on Vercel.

A member of the Team first needs to authorize it.

URL.pathname keeps the leading slash on a drive URL, so a link to
C:/Users/me became C:\C:\Users\me and the folder could not be selected.
decodeURIComponent also threw URIError on a stray percent, which surfaced
as a failed request instead of a path that does not exist. Decode each
escape on its own and drop the URL drive slash.
Decoding each %XX escape on its own never decoded a multi-byte UTF-8
character, and URL percent-encodes non-ASCII even in raw input, so a
folder named café or 数据 no longer resolved on macOS or Linux. Stripping
the drive slash on every platform also made file:///C:/x cwd-relative on
POSIX. fileURLToPath decodes UTF-8 and gives C:\x and \\server\share on
Windows; a stray % is escaped before it (it throws there), and a remote
host off Windows, an encoded slash or a non-UTF-8 escape fall back to the
leniently decoded pathname.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ishaan1124

Copy link
Copy Markdown
Member

I pushed one commit (63bf11d) on top of yours. Decoding each %XX escape on its own broke folders with non-ASCII names on every platform (file:///Users/me/café became …/caf%C3%A9), and the drive-slash strip made file:///C:/x cwd-relative on POSIX. fromFileUrl now uses fileURLToPath (UTF-8, C:\\x, \\\\server\\share on Windows), escapes a stray % first, and falls back without throwing. The tests cover non-ASCII paths and use a platform-conditional drive-letter expectation. CI is green.

@ishaan1124
Ishaan Gangwani (ishaan1124) merged commit fac19d0 into synthetic-sciences:main Sep 29, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Choosing a folder by file:// link doubles the Windows drive letter and throws on a stray percent

2 participants