Skip to content

Redirect directory requests missing trailing slash#252

Open
vibhor-aggr wants to merge 3 commits into
koajs:masterfrom
vibhor-aggr:fix/directory-slash-redirect
Open

Redirect directory requests missing trailing slash#252
vibhor-aggr wants to merge 3 commits into
koajs:masterfrom
vibhor-aggr:fix/directory-slash-redirect

Conversation

@vibhor-aggr

@vibhor-aggr vibhor-aggr commented Jun 11, 2026

Copy link
Copy Markdown

Fixes #130.

This adds a guarded directory redirect before delegating to koa-send: when a GET/HEAD request targets a directory without a trailing slash and the configured index file exists, koa-static redirects to the slash-appended URL while preserving the query string.

The change keeps the existing explicit options intact:

  • format: true still serves /dir directly.
  • format: false still disables directory index formatting.
  • directories without an index file do not redirect.

Verification:

  • npm test
  • npm run lint
  • git diff --check

Summary by Sourcery

Handle directory requests without trailing slashes by redirecting to the slash-appended URL when an index file exists, while preserving existing directory handling modes.

New Features:

  • Add guarded redirect for GET/HEAD directory requests missing a trailing slash when an index file exists, preserving the query string.

Documentation:

  • Document the new format option behavior for directory request handling modes.

Tests:

  • Add tests covering redirect behavior for directory requests without trailing slash in both standard and defer modes, including cases without an index file.

Summary by CodeRabbit

  • New Features

    • Directory requests missing a trailing slash now optionally redirect to the slash-suffixed path (preserves query strings); behavior is controlled by the new format option.
  • Bug Fixes

    • Redirects only occur for GET/HEAD and when a directory index exists; directories without an index return 404.
  • Documentation

    • API docs updated to describe the new format option and directory handling.
  • Tests

    • Added tests covering redirect behavior, query-string preservation, and non-redirect cases.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@vibhor-aggr, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 10 minutes and 47 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 848d7437-d78f-4c81-b6dd-97aac7990b78

📥 Commits

Reviewing files that changed from the base of the PR and between a908b93 and bf3ef56.

📒 Files selected for processing (2)
  • index.js
  • test/index.js
📝 Walkthrough

Walkthrough

This PR implements automatic trailing-slash redirect for directory requests in koa-static. When a request targets a directory containing an index file, the middleware now redirects the request to include a trailing slash, preserving query parameters. A new format option controls this behavior, allowing users to disable the redirect. The implementation adds filesystem checks, integrates redirect logic into both deferred and non-deferred request handlers, includes comprehensive test coverage for both modes, and documents the new option in the README.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding directory redirect functionality for requests missing trailing slashes.
Linked Issues check ✅ Passed The PR implementation directly addresses issue #130 by adding directory redirect logic when URLs lack trailing slashes but an index file exists, preserving query strings.
Out of Scope Changes check ✅ Passed All changes are scoped to the directory redirect feature: README documentation updates, core redirect implementation, test fixture addition, and comprehensive test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

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

@sourcery-ai

sourcery-ai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a guarded pre-send redirect for directory requests missing a trailing slash, including supporting utilities and tests, while documenting the new format directory-handling behavior.

Sequence diagram for pre-send directory redirect in koa-static middleware

sequenceDiagram
  actor Client
  participant KoaApp
  participant serve
  participant redirectToDirectorySlash
  participant send

  Client->>KoaApp: HTTP GET /dir
  KoaApp->>serve: invoke middleware
  serve->>redirectToDirectorySlash: redirectToDirectorySlash(ctx, opts)
  alt redirect performed
    redirectToDirectorySlash-->>serve: true
    serve-->>KoaApp: return (no send)
    KoaApp-->>Client: 301/302 Location /dir/
  else no redirect
    redirectToDirectorySlash-->>serve: false
    serve->>send: send(ctx, ctx.path, opts)
    send-->>serve: done
    serve-->>KoaApp: return
    KoaApp-->>Client: static file response
  end
Loading

File-Level Changes

Change Details Files
Introduce a pre-send redirect for directory requests missing a trailing slash when an index file exists, while preserving existing format behaviors and security constraints.
  • Wrap the main send logic to first attempt a conditional redirect via a new helper before delegating to koa-send, for both immediate and deferred serving modes.
  • Implement redirectToDirectorySlash to validate options, decode the path, resolve it safely within the configured root, respect hidden file configuration, and perform filesystem checks ensuring a directory with an index file before issuing a redirect that preserves the query string.
  • Add utility helpers for safe path decoding, hidden-path detection, specific not-found error detection, and safe resolution from the configured root directory.
index.js
Add tests to verify redirect behavior and non-redirect behavior for directory requests without a trailing slash under different configurations.
  • Add tests confirming that GET requests to directories without trailing slashes are redirected to the slash-suffixed URL with query string preserved for both standard and defer: true configurations.
  • Add a fixture directory without an index file and corresponding test to assert that such directories do not redirect and instead yield a 404.
test/index.js
test/fixtures/empty/hello.txt
Document the new format directory handling semantics in the README.
  • Describe the default redirect behavior for directory requests without trailing slashes when an index file exists and how format: true and format: false affect this behavior.
Readme.md

Assessment against linked issues

Issue Objective Addressed Explanation
#130 Implement redirect behavior so that when a requested URL targets a directory containing an index file but is missing a trailing slash, the server responds with a redirect to the same path with a trailing slash (without changing existing explicit options/behaviors).

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In the defer path, redirectToDirectorySlash is called for all methods, so POST/PUT requests to a directory could now result in a redirect; consider mirroring the existing GET/HEAD guard inside redirectToDirectorySlash (or before calling it) to avoid changing semantics for non-idempotent methods.
  • The redirect URL is constructed with ctx.path + '/' + ctx.search; it may be safer to normalize ctx.search (e.g., ensure it is an empty string or already includes a leading ?) to avoid accidental 'undefined' or double-? issues across different Koa versions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `defer` path, `redirectToDirectorySlash` is called for all methods, so POST/PUT requests to a directory could now result in a redirect; consider mirroring the existing GET/HEAD guard inside `redirectToDirectorySlash` (or before calling it) to avoid changing semantics for non-idempotent methods.
- The redirect URL is constructed with `ctx.path + '/' + ctx.search`; it may be safer to normalize `ctx.search` (e.g., ensure it is an empty string or already includes a leading `?`) to avoid accidental `'undefined'` or double-`?` issues across different Koa versions.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
test/index.js (1)

254-265: ⚡ Quick win

Add deferred-mode negative case for directory without index.

Lines 254-265 validate the redirect success path, but defer: true is still missing the mirrored “no index file => no redirect/404” assertion already covered in Lines 134-142 for non-deferred mode. Add that companion test to cover both branches in deferred flow.

Suggested test addition
+        it('should not redirect when the directory has no index file', function (done) {
+          const app = new Koa()
+
+          app.use(serve('test/fixtures', {
+            defer: true
+          }))
+
+          request(app.listen())
+            .get('/empty')
+            .expect(404, done)
+        })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/index.js` around lines 254 - 265, Add a companion deferred-mode negative
test mirroring the non-deferred case at lines 134-142: create an it(...) named
like "does not redirect directory requests missing a trailing slash in deferred
mode when no index file", use app.use(serve('test/fixtures', { defer: true }))
(same fixtures used by the non-deferred test), request the directory path used
in the non-deferred negative test (e.g., '/noindex' or the same directory name
from lines 134-142) with query params, and assert that the response is a 404 (or
no redirect) and that there is no Location header (use
request(app.listen()).get(...).expect(404) and an assertion that
res.headers.location is undefined). Ensure the new test sits alongside the
existing deferred redirect test so both branches are covered.
index.js (1)

128-128: 💤 Low value

Replace deprecated substr with substring or slice.

String.prototype.substr is deprecated. Use substring or slice instead for the same behavior.

♻️ Suggested fix
 function resolveFromRoot (root, pathname) {
-  const filename = pathname.substr(path.parse(pathname).root.length)
+  const filename = pathname.substring(path.parse(pathname).root.length)
   const resolved = path.resolve(root, filename)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@index.js` at line 128, Replace the deprecated String.prototype.substr call
used to compute filename: instead of
pathname.substr(path.parse(pathname).root.length) use a non-deprecated method
(e.g., pathname.substring(path.parse(pathname).root.length) or
pathname.slice(path.parse(pathname).root.length)) so that the filename
calculation (variable filename derived from pathname and
path.parse(pathname).root.length) behaves identically without using substr.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@index.js`:
- Line 128: Replace the deprecated String.prototype.substr call used to compute
filename: instead of pathname.substr(path.parse(pathname).root.length) use a
non-deprecated method (e.g.,
pathname.substring(path.parse(pathname).root.length) or
pathname.slice(path.parse(pathname).root.length)) so that the filename
calculation (variable filename derived from pathname and
path.parse(pathname).root.length) behaves identically without using substr.

In `@test/index.js`:
- Around line 254-265: Add a companion deferred-mode negative test mirroring the
non-deferred case at lines 134-142: create an it(...) named like "does not
redirect directory requests missing a trailing slash in deferred mode when no
index file", use app.use(serve('test/fixtures', { defer: true })) (same fixtures
used by the non-deferred test), request the directory path used in the
non-deferred negative test (e.g., '/noindex' or the same directory name from
lines 134-142) with query params, and assert that the response is a 404 (or no
redirect) and that there is no Location header (use
request(app.listen()).get(...).expect(404) and an assertion that
res.headers.location is undefined). Ensure the new test sits alongside the
existing deferred redirect test so both branches are covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b35caeec-c1b2-468c-8d08-b5ae064a592d

📥 Commits

Reviewing files that changed from the base of the PR and between 2e871e1 and 46dc57f.

📒 Files selected for processing (4)
  • Readme.md
  • index.js
  • test/fixtures/empty/hello.txt
  • test/index.js

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.

Folders are not redirected if missing a slash at the end

1 participant