Skip to content

Conversation

@efectn
Copy link
Member

@efectn efectn commented Aug 10, 2025

Description

This issue fixes the browsing issue of sub directories when using the static middleware. The issue arises as sanitizePath removes the trailing slash whereas fasthttp fs redirects to index pages only if the path has trailing slash. Fixed it by putting old trailing slash to the end of the path. https://github.com/valyala/fasthttp/blob/master/fs.go#L1105

Fixes #3667

Benchmarks:

Old:

Benchmark_SanitizePath/nilFS_-_urlencoded_chars-16         	 3589934	       336.7 ns/op	     120 B/op	       4 allocs/op
Benchmark_SanitizePath/dirFS_-_urlencoded_chars-16         	 2989479	       404.1 ns/op	     144 B/op	       5 allocs/op
Benchmark_SanitizePath/nilFS_-_slashes-16                  	 3267181	       359.5 ns/op	     160 B/op	       5 allocs/op

New:

Benchmark_SanitizePath/nilFS_-_urlencoded_chars-16         	 3438345	       350.3 ns/op	     120 B/op	       4 allocs/op
Benchmark_SanitizePath/dirFS_-_urlencoded_chars-16         	 2993031	       402.1 ns/op	     144 B/op	       5 allocs/op
Benchmark_SanitizePath/nilFS_-_slashes-16                  	 3304844	       364.9 ns/op	     160 B/op	       5 allocs/op

Changes introduced

List the new features or adjustments introduced in this pull request. Provide details on benchmarks, documentation updates, changelog entries, and if applicable, the migration guide.

  • Benchmarks: Describe any performance benchmarks and improvements related to the changes.
  • Documentation Update: Detail the updates made to the documentation and links to the changed files.
  • Changelog/What's New: Include a summary of the additions for the upcoming release notes.
  • Migration Guide: If necessary, provide a guide or steps for users to migrate their existing code to accommodate these changes.
  • API Alignment with Express: Explain how the changes align with the Express API.
  • API Longevity: Discuss the steps taken to ensure that the new or updated APIs are consistent and not prone to breaking changes.
  • Examples: Provide examples demonstrating the new features or changes in action.

Type of change

  • Enhancement (improvement to existing features and functionality)

Checklist

Before you submit your pull request, please make sure you meet these requirements:

  • Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Updated the documentation in the /docs/ directory for Fiber's documentation.
  • Added or updated unit tests to validate the effectiveness of the changes or new features.
  • Ensured that new and existing unit tests pass locally with the changes.
  • Verified that any new dependencies are essential and have been agreed upon by the maintainers/community.
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

Commit formatting

Please use emojis in commit messages for an easy way to identify the purpose or intention of a commit. Check out the emoji cheatsheet here: CONTRIBUTING.md

Copilot AI review requested due to automatic review settings August 10, 2025 10:47
@efectn efectn requested a review from a team as a code owner August 10, 2025 10:47
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 10, 2025

Walkthrough

A new CSS test file was added for static file serving tests. The sanitizePath function in the static middleware was updated to preserve trailing slashes in sanitized paths. Corresponding tests were expanded to verify correct file serving and browsing behavior for nested directories and CSS files.

Changes

Cohort / File(s) Change Summary
Test Data Addition
.github/testdata/fs/css/test/style2.css
Added a CSS file with an h1 color rule for use in static file serving tests.
Static Path Sanitization Logic
middleware/static/static.go
Modified sanitizePath to retain trailing slashes in the sanitized output when present in the input.
Static File Serving Tests
middleware/static/static_test.go
Added tests for serving a CSS file from a nested directory and verified correct content and status.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant StaticMiddleware
    participant FileSystem

    Client->>StaticMiddleware: GET /dirfs/test/style2.css
    StaticMiddleware->>StaticMiddleware: sanitizePath("/dirfs/test/style2.css")
    StaticMiddleware->>FileSystem: Open sanitized path
    FileSystem-->>StaticMiddleware: Return file contents
    StaticMiddleware-->>Client: Respond with CSS file and 200 status
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~7 minutes

Assessment against linked issues

Objective Addressed Explanation
Fix static file browsing for subfolders so that subpaths display correct file trees (#3667)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Possibly related PRs

Suggested labels

☢️ Bug, 🧹 Updates, v3

Suggested reviewers

  • gaby
  • sixcolors
  • ReneWerner87

Poem

A bunny hopped through static files,
Trailing slashes brought new smiles.
Subfolders now display with glee,
Browsing works as it should be!
With tests and code both shining bright,
This patch brings static joy and light.
🐇✨

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch static-subfolder-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @efectn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

I've addressed an issue in the static middleware where browsing subdirectories failed because the sanitizePath function incorrectly removed trailing slashes from paths. This fix ensures that if an original path had a trailing slash, it is preserved in the sanitized path, allowing fasthttp to correctly identify and serve content from directories. This resolves the problem of accessing index pages or files within subdirectories when a trailing slash is present in the request URL.

Highlights

  • Fix for Static Middleware Subdirectory Browsing: The sanitizePath function in middleware/static/static.go has been updated to correctly handle paths with trailing slashes. Previously, sanitizePath would remove trailing slashes, which caused issues with fasthttp's file system handling for subdirectories.
  • Preservation of Trailing Slashes: A new hasTrailingSlash boolean is introduced to check if the original path had a trailing slash. If so, and the sanitized path is not just a root path and doesn't end with a slash, a trailing slash is re-appended.
  • New Test Case for Subdirectory Browsing: A new CSS file (.github/testdata/fs/css/test/style2.css) and corresponding test cases in middleware/static/static_test.go have been added to validate that static files within subdirectories are now correctly served.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a subdirectory browsing issue in the static middleware where paths with trailing slashes were being stripped by sanitizePath, preventing fasthttp's filesystem handler from properly redirecting to index pages. The fix preserves the original trailing slash after path sanitization.

  • Fixed trailing slash handling in sanitizePath function to maintain compatibility with fasthttp filesystem redirects
  • Added test coverage for subdirectory browsing functionality
  • Added test file to support the new test cases

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
middleware/static/static.go Modified sanitizePath to preserve trailing slashes when present in original path
middleware/static/static_test.go Added test cases for subdirectory browsing with and without trailing slashes
.github/testdata/fs/css/test/style2.css Added test file to support subdirectory browsing tests

func sanitizePath(p []byte, filesystem fs.FS) ([]byte, error) {
var s string

hasTrailingSlash := len(p) > 0 && p[len(p)-1] == '/'
Copy link

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider extracting the trailing slash check into a named constant or helper function for better readability and reusability.

Copilot uses AI. Check for mistakes.
}
s = "/" + s
}

Copy link

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

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

The condition len(s) > 1 prevents adding a trailing slash to the root path "/", but this logic could be clearer with a comment explaining why root paths are excluded.

Suggested change
// Only add a trailing slash if the path is not the root "/".
// This prevents turning "/" into "//".

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Aug 10, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.83%. Comparing base (fae0860) to head (484b916).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3673      +/-   ##
==========================================
- Coverage   91.84%   91.83%   -0.02%     
==========================================
  Files         113      113              
  Lines       11447    11453       +6     
==========================================
+ Hits        10514    10518       +4     
- Misses        669      671       +2     
  Partials      264      264              
Flag Coverage Δ
unittests 91.83% <100.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively fixes an issue with browsing subdirectories when using the static middleware. The change correctly preserves the trailing slash on directory paths by updating sanitizePath, which prevents an unnecessary redirect from fasthttp. The fix is well-implemented and includes new test cases that validate both the corrected directory browsing behavior and ensure that file serving within subdirectories remains functional. Benchmarks show no performance regression. The changes are solid and address the issue well.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
middleware/static/static_test.go (1)

654-667: Good coverage for nested dir browse and asset; add explicit slash edge-cases

The new checks for /dirfs/test and /dirfs/test/style2.css are on point. To lock in behavior around trailing slashes, consider adding:

  • Directory with explicit slash:
    • GET /dirfs/test/ → 200, Content-Type text/html; charset=utf-8 (index/listing)
  • File with an erroneous trailing slash (should not be treated as a directory):
    • GET /dirfs/style.css/ → 404 (or a redirect if FS changes behavior in the future)

If you adopt the refactor to preserve the original trailing slash, these tests help ensure we don’t accidentally start serving files when a trailing slash is present.

Example additions:

@@ func Test_Static_FS_Browse(t *testing.T) {
-   require.Contains(t, string(body), "color")
+   require.Contains(t, string(body), "color")

+   // Directory explicitly with trailing slash
+   resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/dirfs/test/", nil))
+   require.NoError(t, err, "app.Test(req)")
+   require.Equal(t, 200, resp.StatusCode, "Status code")
+   require.Equal(t, fiber.MIMETextHTMLCharsetUTF8, resp.Header.Get(fiber.HeaderContentType))

+   // File path with trailing slash should not be served as a directory
+   resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/dirfs/style.css/", nil))
+   require.NoError(t, err, "app.Test(req)")
+   require.Equal(t, 404, resp.StatusCode, "Status code")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ca6ebd and 484b916.

📒 Files selected for processing (3)
  • .github/testdata/fs/css/test/style2.css (1 hunks)
  • middleware/static/static.go (2 hunks)
  • middleware/static/static_test.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: repeated
  • GitHub Check: Analyse
  • GitHub Check: Compare
🔇 Additional comments (1)
.github/testdata/fs/css/test/style2.css (1)

1-3: Test asset looks good

CSS fixture is simple and purpose-built for assertions. No issues.

Comment on lines +27 to +28
hasTrailingSlash := len(p) > 0 && p[len(p)-1] == '/'

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Preserve trailing slash from the original request, not from the rewritten path

PathRewrite appends a trailing slash to the working path unconditionally in the default branch. Because hasTrailingSlash is computed from p inside sanitizePath (after that rewrite), it will be true for most non-root requests, including file paths. This means sanitizePath will also append a trailing slash post-clean, producing paths like /style.css/ even when the original request was /style.css.

While fasthttp.FS appears to tolerate this (tests pass), it diverges from the stated intent “preserve the original trailing slash” and risks subtle inconsistencies or future regressions.

Recommended refactor:

  • Compute whether the original request had a trailing slash before any rewriting.
  • Apply the trailing-slash preservation after sanitizePath returns, or pass a flag into sanitizePath.

Pros:

  • Accurately preserves client semantics.
  • Avoids forcing slash on file paths.
  • Keeps sanitizePath single-responsibility (validate/clean).

Cons:

  • Small signature/usage change or extra logic in PathRewrite.

Suggested changes:

  1. Keep sanitizePath focused; remove the post-clean append there:
-    hasTrailingSlash := len(p) > 0 && p[len(p)-1] == '/'
+    // trailing slash preservation is handled by the caller (based on original request)
-    if hasTrailingSlash && len(s) > 1 && s[len(s)-1] != '/' {
-        s += "/"
-    }
+    // caller may re-append a trailing slash after validation/cleaning
  1. In PathRewrite, capture the request’s original trailing slash before mutating path and apply it after sanitizePath:

Go (outside the changed ranges; illustrative patch):

fs.PathRewrite = func(fctx *fasthttp.RequestCtx) []byte {
    raw := fctx.Path() // original request path
    reqHasTrailingSlash := len(raw) > 0 && raw[len(raw)-1] == '/'

    path := fctx.Path()
    // ... existing prefix handling and default-case adjustments ...
    // (keep current logic that ensures leading slash etc.)

    sanitized, err := sanitizePath(path, fs.FS)
    if err != nil {
        return []byte("/__fiber_invalid__")
    }
    if reqHasTrailingSlash && len(sanitized) > 1 && sanitized[len(sanitized)-1] != '/' {
        sanitized = append(sanitized, '/')
    }
    return sanitized
}

This ensures trailing slashes are preserved only when they existed on the client’s request.

To confirm sanitizePath isn’t used elsewhere in a way that would need updating:

Add a short comment explaining why trailing slash is preserved (fasthttp FS directory handling), and why it’s done post-clean to avoid invalid fs.ValidPath(".../").

Also applies to: 72-74


🏁 Script executed:

#!/bin/bash
# Find all sanitizePath call sites and their contexts
rg -n --no-heading "sanitizePath\\(" -A 3 -B 3

# Inspect rewrite code to verify trailing slash appending occurs pre-sanitize
rg -n --no-heading "PathRewrite\\s*=\\s*func\\(" -A 120 | sed -n '1,200p'

Length of output: 8863


🏁 Script executed:

#!/bin/bash
# Display the full sanitizePath implementation for review
sed -n '1,120p' middleware/static/static.go

Length of output: 2546


Preserve the client’s original trailing-slash, not the one injected by PathRewrite

Currently, PathRewrite unconditionally appends “/” whenever it strips the route prefix for directories, which makes
sanitizePath see a trailing slash on almost every non-file request—and then re-appends it post-clean. This diverges from the intent (“preserve only the original trailing slash”) and forces e.g. /style.css/style.css/ even if the browser never asked for it.

Key locations to update:

  • sanitizePath (middleware/static/static.go:27–28 & forty-seven lines later)
    • Remove the hasTrailingSlash := … and the if hasTrailingSlash … s += “/” block
  • PathRewrite default branch (middleware/static/static.go:163–166)
    • Before you mangle path, record reqHasSlash := rawPath[len(rawPath)-1] == '/'
    • After sanitizePath(...), re-append “/” only if reqHasSlash was true

Suggested diffs:

--- a/middleware/static/static.go
+++ b/middleware/static/static.go
@@ func sanitizePath(p []byte, filesystem fs.FS) ([]byte, error) {
-   hasTrailingSlash := len(p) > 0 && p[len(p)-1] == '/'-   if hasTrailingSlash && len(s) > 1 && s[len(s)-1] != '/' {
-       s += "/"
-   }
+   // trailing-slash preservation is handled by PathRewrite (caller) based on the original request
--- a/middleware/static/static.go
+++ b/middleware/static/static.go
@@ fs.PathRewrite = func(fctx *fasthttp.RequestCtx) []byte {
-   path := fctx.Path()
+   raw := fctx.Path()
+   reqHasSlash := len(raw) > 0 && raw[len(raw)-1] == '/'

    path := raw
    …  // existing prefixLogic
    // default (non-file) branch:
-       if len(path) == 0 || path[len(path)-1] != '/' {
-           path = append(path, '/')
-       }
+       // do not append here; sanitizePath will clean, and we’ll re-append below
--- a/middleware/static/static.go
+++ b/middleware/static/static.go
@@ fs.PathRewrite = func(fctx *fasthttp.RequestCtx) []byte {
    sanitized, err := sanitizePath(path, fs.FS)
    if err != nil {
        return []byte("/__fiber_invalid__")
    }
+   if reqHasSlash && len(sanitized) > 1 && sanitized[len(sanitized)-1] != '/' {
+       sanitized = append(sanitized, '/')
+   }
    return sanitized
}

These changes will:
• Accurately preserve only the client-provided slash.
• Keep sanitizePath focused on cleaning/validation.
• Require no other call sites to change (sanitizePath is only used here and in its own tests).
• (Optional) Add a comment on why trailing-slash logic lives in PathRewrite—so future readers won’t re-introduce it in sanitizePath.

🤖 Prompt for AI Agents
In middleware/static/static.go around lines 27-28 and 74-75, remove the code
that detects and re-appends a trailing slash in sanitizePath to stop preserving
slashes added by PathRewrite. Then, in the PathRewrite function around lines
163-166, before modifying the path, record whether the original request path had
a trailing slash in a variable like reqHasSlash. After calling sanitizePath,
re-append the trailing slash only if reqHasSlash is true. This ensures only the
client-provided trailing slash is preserved, keeping sanitizePath focused on
cleaning and validation.

@gaby gaby added the v3 label Aug 10, 2025
@gaby gaby changed the title static: fix browser does not work with subdirs 🐛 bug: Fix Static middleware browser support for subdirectories Aug 10, 2025
@ReneWerner87 ReneWerner87 added this to the v3 milestone Aug 11, 2025
@ReneWerner87 ReneWerner87 merged commit c3ecca1 into main Aug 11, 2025
18 of 21 checks passed
@ReneWerner87 ReneWerner87 deleted the static-subfolder-fix branch August 11, 2025 10:32
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.

🐛 [Bug]: Static - browsing is broken for subfolders

4 participants