Skip to content

Conversation

@yusukebe
Copy link
Member

@yusukebe yusukebe commented Jul 20, 2025

This PR introduces a new mechanism for the serve static middleware. This can fix unintended path resolution.

  • Duplicated pathResolve option
  • Added join option
  • Simplified src/middleware/serve-static/index.ts
  • Implemented defaultJoin. It is used if the join option is not specified
  • Updated serve static for deno/bun
  • Fixed some tests to support new functions

It is inspired by honojs/node-server#261

Problems

In previous implementations, paths starting with C:\Users\yusuke\ on Windows were converted to /Users/yusuke. This caused unintended behavior because the drive name was lost.

If we use a function such as join exported by path:node, we can solve this problem and simplify the code. The pathResolve option is also unnecessary.

Breaking changes?

I changed the test code, but only path passed to onFound and onNotFound has been slightly changed.

Other behaviors remain unchanged. Additionally, unexpected path resolution issues should be resolved immediately. Release a minor version without changing the major version.

The author should do the following, if applicable

  • Add tests
  • Run tests
  • bun run format:fix && bun run lint:fix to format the code
  • Add TSDoc/JSDoc to document the code

@yusukebe yusukebe marked this pull request as draft July 20, 2025 09:24
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@codecov
Copy link

codecov bot commented Jul 23, 2025

Codecov Report

❌ Patch coverage is 83.33333% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.51%. Comparing base (89f4c96) to head (f3ee780).
⚠️ Report is 31 commits behind head on next.

Files with missing lines Patch % Lines
src/middleware/serve-static/path.ts 68.18% 7 Missing ⚠️
src/middleware/serve-static/index.ts 95.45% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             next    #4291      +/-   ##
==========================================
- Coverage   91.61%   91.51%   -0.10%     
==========================================
  Files         170      171       +1     
  Lines       10875    10776      -99     
  Branches     3099     3085      -14     
==========================================
- Hits         9963     9862     -101     
- Misses        911      913       +2     
  Partials        1        1              

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

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.


it('Should return index.html', async () => {
// Serve static on Cloudflare Workers cannot determine whether the target path is a directory or not
it.skip('Should return index.html', async () => {
Copy link
Member Author

Choose a reason for hiding this comment

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

In the previous implementation, if the file was not found, it checked whether index.html existed. This PR, that process has been removed.

Since serve static for Cloudflare Workers is deprecated and not planned to be used, this change is acceptable.

@yusukebe yusukebe marked this pull request as ready for review July 23, 2025 11:24
@yusukebe
Copy link
Member Author

Hey @usualoma !

What do you think of this? If it makes sense for you, please review it!

@usualoma
Copy link
Member

Hi @yusukebe
Thanks for all your hard work! Just give me a few days to review everything.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@@ -1,3 +1,4 @@
import { join } from 'node:path'
Copy link
Member Author

Choose a reason for hiding this comment

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

We can use @std/path, but it's better to use node:path because it's built into Deno.

expect(await res.text()).toBe('Hello in ./static/sub/index.html')
})

it('Should return 200 response - /static/helloworld', async () => {
Copy link
Member Author

Choose a reason for hiding this comment

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

This test is the same as Should return 200 response - /static/sub?? We may remove this.

@yusukebe
Copy link
Member Author

yusukebe commented Aug 2, 2025

Hi @usualoma Thank you for the accurate comment!

For Deno, as you said, we can use node:path's join. Updated: b3738f7

For the separators issue, I made defaultJoin support only Linux (posix), and it does not support Windows. This means if runtime-depends serve static-middleware (adapter/bun/serve-static.ts or adapter/deno/serve-static.ts) does not specify path option, it can't handle Windows paths. But, I think it's okay to cut Windows support because these runtime-dependent serve static-middleware can use join in the runtime API.

What do you think of this?

@yusukebe
Copy link
Member Author

yusukebe commented Aug 5, 2025

Hi @usualoma

If you already realized this, sorry for that. I updated this PR. Can you review this?

@usualoma
Copy link
Member

usualoma commented Aug 5, 2025

Hi @yusukebe

Thanks for the update. The content makes sense!

As it stands, the frequency of defaultJoin usage is very low, so I think it's important to keep the code size small, so I think it would be better to do the following.

diff --git i/src/middleware/serve-static/path.ts w/src/middleware/serve-static/path.ts
index b8ae7986..0253cf5d 100644
--- i/src/middleware/serve-static/path.ts
+++ w/src/middleware/serve-static/path.ts
@@ -3,39 +3,23 @@
  * If you need Windows path support, please use `join` exported from `node:path` etc. instead.
  */
 export const defaultJoin = (...paths: string[]): string => {
-  if (paths.length === 0) {
-    return '.'
-  }
-  if (paths.length === 1 && paths[0] === '') {
-    return '.'
-  }
-
   // Join non-empty paths with '/'
   let result = paths.filter((p) => p !== '').join('/')
 
   // Normalize multiple slashes to single slash
-  result = result.replace(/\/+/g, '/')
+  result = result.replace(/(?<=\/)\/+/g, '')
 
   // Handle path resolution (. and ..)
   const segments = result.split('/')
   const resolved = []
-  const isAbsolute = result.startsWith('/')
 
   for (const segment of segments) {
-    if (segment === '' || segment === '.') {
-      continue
-    }
-    if (segment === '..') {
-      if (resolved.length > 0 && resolved[resolved.length - 1] !== '..') {
-        resolved.pop()
-      } else if (!isAbsolute) {
-        resolved.push('..')
-      }
-    } else {
+    if (segment === '..' && resolved.length > 0 && resolved.at(-1) !== '..') {
+      resolved.pop()
+    } else if (segment !== '.') {
       resolved.push(segment)
     }
   }
 
-  const final = resolved.join('/')
-  return isAbsolute ? '/' + final : final || '.'
+  return resolved.join('/') || '.'
 }

Co-authored-by: Taku Amano <[email protected]>
@github-actions
Copy link

github-actions bot commented Aug 5, 2025

Bundle size check

main (23c6d5a) #4291 (12a0eae) +/-
Bundle Size (B) 18,320B 18,320B 0B
Bundle Size (KB) 17.89K 17.89K 0K

Compiler Diagnostics (tsc)

main (23c6d5a) #4291 (12a0eae) +/-
Files 262 262 0
Lines 116,395 116,395 0
Identifiers 114,321 114,321 0
Symbols 259,914 259,914 0
Types 162,579 162,579 0
Instantiations 3,037,134 3,037,134 0
Memory used 273,306K 269,447K -3,859K
I/O read 0.02s 0.03s 0.01s
I/O write 0s 0s 0s
Parse time 0.76s 0.65s -0.11s
Bind time 0.33s 0.29s -0.04s
Check time 3.98s 3.82s -0.16s
Emit time 0s 0s 0s
Total time 5.07s 4.76s -0.31s

Compiler Diagnostics (typescript-go)

main (23c6d5a) #4291 (12a0eae) +/-
Files 232 232 0
Lines 106,294 106,294 0
Identifiers 106,061 106,061 0
Symbols 371,546 371,546 0
Types 293,026 293,026 0
Instantiations 3,564,730 3,564,730 0
Memory used 229,697K 229,796K 99K
Memory allocs 9,996,685 9,996,991 306
Parse time 0.076s 0.09s 0.014s
Bind time 0.016s 0.041s 0.025s
Check time 1.413s 1.623s 0.21s
Emit time 0s 0s 0s
Total time 1.509s 1.757s 0.248s

Reported by octocov

@github-actions
Copy link

github-actions bot commented Aug 5, 2025

HTTP Performance Benchmark

Framework Runtime Average Ping Query Body
hono (origin/main) bun 36,843.75 50,699.26 31,764.57 28,067.41
hono (current) bun 36,461.82 49,982.22 31,665.30 27,737.93
Change -1.04% -1.41% -0.31% -1.17%

@yusukebe yusukebe requested a review from usualoma August 5, 2025 19:50
@yusukebe
Copy link
Member Author

yusukebe commented Aug 5, 2025

@usualoma Thanks! That's good. I applied the patch: f3ee780

Does this look good to you?

@yusukebe yusukebe added the v4.9 label Aug 5, 2025
@usualoma
Copy link
Member

usualoma commented Aug 5, 2025

Hi @yusukebe
Thank you. I think it's good!

@yusukebe
Copy link
Member Author

yusukebe commented Aug 5, 2025

@usualoma Thanks!

@yusukebe yusukebe changed the base branch from main to next August 7, 2025 11:18
@yusukebe yusukebe merged commit 1196f14 into next Aug 7, 2025
22 checks passed
@yusukebe yusukebe deleted the feat/serve-static-use-join branch August 7, 2025 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants