From 0fcf44a938354fb7de215c7deffa8055b407bde0 Mon Sep 17 00:00:00 2001 From: Nazmul Hossain Date: Tue, 11 Aug 2026 15:37:18 +0600 Subject: [PATCH 1/3] fix: preserve a req.url rewritten inside a mounted layer When a layer mounted with a path is entered and nothing is left of the path, the router injects a leading slash into req.url. That slash used to be removed unconditionally on the way out, so when the layer rewrote req.url the real leading slash was stripped and the mount path was restored without a separator (/app + /index.html became /appindex.html). Remember the exact URL the injected slash produced and only strip it again when the layer left req.url untouched. Ref: expressjs/express#4059 --- index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 4358aebd..f9bce07f 100644 --- a/index.js +++ b/index.js @@ -158,7 +158,7 @@ Router.prototype.handle = function handle (req, res, callback) { const protohost = getProtohost(req.url) || '' let removed = '' const self = this - let slashAdded = false + let slashAddedUrl = null let sync = 0 const paramcalled = {} @@ -190,10 +190,10 @@ Router.prototype.handle = function handle (req, res, callback) { ? null : err - // remove added slash - if (slashAdded) { - req.url = req.url.slice(1) - slashAdded = false + // remove added slash unless the layer rewrote req.url + if (slashAddedUrl !== null) { + if (req.url === slashAddedUrl) req.url = req.url.slice(1) + slashAddedUrl = null } // restore altered req.url @@ -325,7 +325,7 @@ Router.prototype.handle = function handle (req, res, callback) { // Ensure leading slash if (!protohost && req.url[0] !== '/') { req.url = '/' + req.url - slashAdded = true + slashAddedUrl = req.url } // Setup base URL (no trailing slash) From 008753710a6189f2642f032407426005d30e9b50 Mon Sep 17 00:00:00 2001 From: Nazmul Hossain Date: Tue, 11 Aug 2026 15:40:03 +0600 Subject: [PATCH 2/3] test: cover req.url rewritten inside a mounted layer Regression tests for the mount path being restored without a separator when a layer rewrote req.url, including the case where the original url carried a query string. --- test/router.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/router.js b/test/router.js index b440e404..b2915ae5 100644 --- a/test/router.js +++ b/test/router.js @@ -1393,6 +1393,36 @@ describe('Router', function () { .expect('x-saw-1', 'GET /') .expect(200, 'saw GET /foo/', done) }) + + it('should restore a req.url rewritten inside the layer', function (done) { + const router = new Router() + const server = createServer(router) + + router.use('/foo', function (req, res, next) { + req.url = '/bar' + next() + }) + router.use(saw) + + request(server) + .get('/foo') + .expect(200, 'saw GET /foo/bar', done) + }) + + it('should restore a req.url rewritten inside the layer when a query string was present', function (done) { + const router = new Router() + const server = createServer(router) + + router.use('/foo', function (req, res, next) { + req.url = '/bar' + next() + }) + router.use(saw) + + request(server) + .get('/foo?fizz=buzz') + .expect(200, 'saw GET /foo/bar', done) + }) }) }) From 1b53c8a6082f7ed35a27b35cbf3cec00e8f4a12d Mon Sep 17 00:00:00 2001 From: Nazmul Hossain Date: Tue, 11 Aug 2026 15:41:44 +0600 Subject: [PATCH 3/3] docs: document how a rewritten req.url is restored under a mount path The stripped mount path is only mentioned as being removed, not as being added back, which makes the behavior of rewriting req.url inside a mounted middleware unclear. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 156c380c..83d64cb2 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,10 @@ consider it one for handling `OPTIONS` requests. * Note: If a `path` is specified, that `path` is stripped from the start of `req.url`. +* Note: The stripped `path` is added back to `req.url` when the middleware calls + `next()`. A `req.url` rewritten by the middleware is treated as relative to the + mount `path`, so rewriting `req.url` to `/index.html` inside a router mounted + on `/app` continues with `/app/index.html`.