From 6e716997fa22b65b0c78931fc6eb648fb9b2f5a1 Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Sun, 9 Jun 2024 12:00:46 -0700 Subject: [PATCH] Improve normalisation of percent-encoded request URLs Summary: When processing requests, Metro compares path prefixes and "extensions" to specific magic strings, eg `/[metro-project]/`. Currently, this matching is performed on the literal requested path, which makes it sensitive to URL (percent) encoding. In particular, iOS (`NSURL urlWithString`) encodes some characters more aggressively than other clients, including encoding `[` and `]`. This causes a request for `/[metro-project]/foo.js` to fail with a 404 when executed through `NSURLRequest`, because `urlObj.pathname` begins `/%5Bmetro-project%5D/`. Metro should ideally be insensitive to percent-encoding on all requests. This fix isn't complete, but addresses the particular blocking issue with source URLs. ``` - **[Fix]**: Improve dev server insensitivity to percent encoding on source requests. ``` Reviewed By: motiz88 Differential Revision: D58289369 fbshipit-source-id: 616da5d5e44539996c16f103254f1b5aaa83704c --- packages/metro/src/Server.js | 17 ++++++----------- .../integration_tests/__tests__/server-test.js | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/metro/src/Server.js b/packages/metro/src/Server.js index ce94a10fe9..a1b01a9ee8 100644 --- a/packages/metro/src/Server.js +++ b/packages/metro/src/Server.js @@ -540,7 +540,7 @@ class Server { ) { const originalUrl = req.url; req.url = this._rewriteAndNormalizeUrl(req.url); - const urlObj = url.parse(req.url, true); + const urlObj = url.parse(decodeURI(req.url), true); const {host} = req.headers; debug( `Handling request: ${host ? 'http://' + host : ''}${req.url}` + @@ -597,11 +597,11 @@ class Server { for (const [pathnamePrefix, normalizedRootDir] of this ._sourceRequestRoutingMap) { if (pathname.startsWith(pathnamePrefix)) { + const relativePathname = pathname.substr(pathnamePrefix.length); await this._processSourceRequest( - req, - res, - pathnamePrefix, + relativePathname, normalizedRootDir, + res, ); handled = true; break; @@ -614,15 +614,10 @@ class Server { } async _processSourceRequest( - req: IncomingMessage, - res: ServerResponse, - pathnamePrefix: string, + relativePathname: string, rootDir: string, + res: ServerResponse, ): Promise { - const urlObj = url.parse(req.url, true); - const relativePathname = nullthrows(urlObj.pathname).substr( - pathnamePrefix.length, - ); if ( !this._allowedSuffixesForSourceRequests.some(suffix => relativePathname.endsWith(suffix), diff --git a/packages/metro/src/integration_tests/__tests__/server-test.js b/packages/metro/src/integration_tests/__tests__/server-test.js index 5ab5c3c9b1..f4ee32d5f2 100644 --- a/packages/metro/src/integration_tests/__tests__/server-test.js +++ b/packages/metro/src/integration_tests/__tests__/server-test.js @@ -195,5 +195,20 @@ describe('Metro development server serves bundles via HTTP', () => { ), ); }); + + test('requested with aggressive URL encoding /%5Bmetro-project%5D', async () => { + const response = await fetch( + 'http://localhost:' + + config.server.port + + '/%5Bmetro-project%5D/Foo%2Ejs', + ); + expect(response.status).toBe(200); + expect(await response.text()).toEqual( + await fs.promises.readFile( + path.join(__dirname, '../basic_bundle/Foo.js'), + 'utf8', + ), + ); + }); }); });