Skip to content

fix(serve-static): normalize all backslashes in file paths, not just the first - #4962

Merged
yusukebe merged 1 commit into
honojs:mainfrom
francisjohnjohnston-web:pil-c0035-filepath-backslash-fix
May 24, 2026
Merged

yusukebe merged 1 commit into
honojs:mainfrom
francisjohnjohnston-web:pil-c0035-filepath-backslash-fix

Conversation

@francisjohnjohnston-web

Copy link
Copy Markdown

What

getFilePathWithoutDefaultDocument in src/utils/filepath.ts normalizes backslash path separators to forward slashes so a requested path resolves consistently regardless of separator style. The replacement uses a non-global regex:

// foo\bar.txt => foo/bar.txt
filename = filename.replace(/\\/, '/')

Without the g flag, only the first backslash is converted, so a path with more than one segment is left with mixed separators:

input current expected
foo\bar\baz.txt foo/bar\baz.txt foo/bar/baz.txt

The remaining \ then resolves to the wrong file (or a 404) on back-ends that don't treat \ as a path separator. The adjacent comment (foo\bar.txt => foo/bar.txt) shows the intent was to normalize all of them.

Fix

Add the g flag:

filename = filename.replace(/\\/g, '/')

Behaviour is unchanged for any path with zero or one backslash, so all existing tests pass. I also added a regression test covering a multi-segment path (with and without root), which returns the broken foo/bar\baz.txt before this change.

Why it was easy to miss

Every existing slashToBackslash(...) test case contains only a single backslash after the leading-separator strip, so the suite stayed green and the missing flag was invisible on a skim.

Checks

  • vitest run src/utils/filepath.test.ts — green (incl. new regression test)
  • tsc --noEmit — clean
  • eslint + prettier — clean
  • Blast radius: one character + one test; no public API change.

…the first

`getFilePathWithoutDefaultDocument` converts backslash separators to slashes so
the same path resolves consistently regardless of the separator style of the
incoming request. The replace used a non-global regex (`/\/`), so only the
FIRST backslash was converted: a path with multiple segments like
`foo\bar\baz.txt` became `foo/bar\baz.txt` — mixed separators that resolve to the
wrong file (or 404) on platforms that don't treat `\` as a separator. The
adjacent comment (`foo\bar.txt => foo/bar.txt`) shows the intent was to
normalize all of them.

Fix: add the `g` flag (`/\/g`). Behaviour is unchanged for any path with zero
or one backslash, so all existing tests pass; added a regression test covering a
multi-segment path (with and without `root`) that returns the broken
`foo/bar\baz.txt` before this change.

tsc --noEmit clean; eslint + prettier clean.
@codecov

codecov Bot commented May 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.77%. Comparing base (2f01b77) to head (2b22d09).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4962   +/-   ##
=======================================
  Coverage   78.77%   78.77%           
=======================================
  Files         154      154           
  Lines       10571    10571           
  Branches     2211     2211           
=======================================
  Hits         8327     8327           
  Misses       2244     2244           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yusukebe yusukebe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@yusukebe

Copy link
Copy Markdown
Member

@francisjohnjohnston-web

It actually needs g. Thank you!

@yusukebe
yusukebe merged commit 82dad62 into honojs:main May 24, 2026
20 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.

2 participants