From a840f5abc1f49539a7dc8b3d13c3fbec0f090450 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 20:38:59 +0300 Subject: [PATCH 01/14] fix(security): do not allow to read files above --- .cspell.json | 3 +- src/middleware.js | 13 +++++++ src/utils/compatibleAPI.js | 4 ++- src/utils/getFilenameFromUrl.js | 49 ++++++++++++++++++------- test/middleware.test.js | 55 ++++++++++++++++++++++++++--- types/utils/getFilenameFromUrl.d.ts | 5 +-- 6 files changed, 107 insertions(+), 22 deletions(-) diff --git a/.cspell.json b/.cspell.json index 490b85431..5fd258574 100644 --- a/.cspell.json +++ b/.cspell.json @@ -18,7 +18,8 @@ "configurated", "mycustom", "commitlint", - "nosniff" + "nosniff", + "deoptimize" ], "ignorePaths": [ "CHANGELOG.md", diff --git a/src/middleware.js b/src/middleware.js index a7de58d4e..fa507f885 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -80,6 +80,18 @@ function wrapper(context) { extra, ); + if (extra.errorCode) { + if (extra.errorCode === 403) { + context.logger.error(`Malicious path "${filename}".`); + } + + sendError(req, res, extra.errorCode, { + modifyResponseData: context.options.modifyResponseData, + }); + + return; + } + if (!filename) { await goNext(); @@ -164,6 +176,7 @@ function wrapper(context) { headers: { "Content-Range": res.getHeader("Content-Range"), }, + modifyResponseData: context.options.modifyResponseData, }); return; diff --git a/src/utils/compatibleAPI.js b/src/utils/compatibleAPI.js index e8e3f5b5f..b168f040d 100644 --- a/src/utils/compatibleAPI.js +++ b/src/utils/compatibleAPI.js @@ -177,6 +177,8 @@ function destroyStream(stream, suppress) { /** @type {Record} */ const statuses = { + 400: "Bad Request", + 403: "Forbidden", 404: "Not Found", 416: "Range Not Satisfiable", 500: "Internal Server Error", @@ -213,7 +215,7 @@ function sendError(req, res, status, options) { // Send basic response setStatusCode(res, status); - setHeaderForResponse(res, "Content-Type", "text/html; charset=UTF-8"); + setHeaderForResponse(res, "Content-Type", "text/html; charset=utf-8"); setHeaderForResponse(res, "Content-Security-Policy", "default-src 'none'"); setHeaderForResponse(res, "X-Content-Type-Options", "nosniff"); diff --git a/src/utils/getFilenameFromUrl.js b/src/utils/getFilenameFromUrl.js index 82fab7447..a5a7c64af 100644 --- a/src/utils/getFilenameFromUrl.js +++ b/src/utils/getFilenameFromUrl.js @@ -43,11 +43,28 @@ const mem = (fn, { cache = new Map() } = {}) => { }; const memoizedParse = mem(parse); +const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/; + /** * @typedef {Object} Extra * @property {import("fs").Stats=} stats + * @property {number=} errorCode + */ + +/** + * decodeURIComponent. + * + * Allows V8 to only deoptimize this fn instead of all of send(). + * + * @param {string} input + * @returns {string} */ +function decode(input) { + return querystring.unescape(input); +} + +// TODO refactor me in the next major release, this function should return `{ filename, stats, error }` /** * @template {IncomingMessage} Request * @template {ServerResponse} Response @@ -85,22 +102,30 @@ function getFilenameFromUrl(context, url, extra = {}) { continue; } - if ( - urlObject.pathname && - urlObject.pathname.startsWith(publicPathObject.pathname) - ) { - filename = outputPath; + let pathname = decode(urlObject.pathname); - // Strip the `pathname` property from the `publicPath` option from the start of requested url - // `/complex/foo.js` => `foo.js` - const pathname = urlObject.pathname.slice( - publicPathObject.pathname.length, - ); + // Null byte(s) + if (pathname.includes("\0")) { + // eslint-disable-next-line no-param-reassign + extra.errorCode = 400; + + return; + } + + if (pathname && pathname.startsWith(publicPathObject.pathname)) { + // ".." is malicious + if (UP_PATH_REGEXP.test(path.normalize(`.${path.sep}${pathname}`))) { + // eslint-disable-next-line no-param-reassign + extra.errorCode = 403; - if (pathname) { - filename = path.join(outputPath, querystring.unescape(pathname)); + return; } + // Strip the `pathname` property from the `publicPath` option from the start of requested url + // `/complex/foo.js` => `foo.js` + pathname = pathname.slice(publicPathObject.pathname.length); + filename = path.join(outputPath, pathname); + try { // eslint-disable-next-line no-param-reassign extra.stats = diff --git a/test/middleware.test.js b/test/middleware.test.js index 755b92fd7..6f5ad0ccb 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -99,6 +99,10 @@ describe.each([ path.resolve(outputPath, "image.svg"), "svg image", ); + instance.context.outputFileSystem.writeFileSync( + path.resolve(outputPath, "image image.svg"), + "svg image", + ); instance.context.outputFileSystem.writeFileSync( path.resolve(outputPath, "byte-length.html"), "\u00bd + \u00bc = \u00be", @@ -263,7 +267,7 @@ describe.each([ `bytes */${codeLength}`, ); expect(response.headers["content-type"]).toEqual( - "text/html; charset=UTF-8", + "text/html; charset=utf-8", ); expect(response.text).toEqual( ` @@ -447,6 +451,29 @@ describe.each([ false, ); }); + + it('should return the "200" code for the "GET" request to the "image image.svg" file', async () => { + const fileData = instance.context.outputFileSystem.readFileSync( + path.resolve(outputPath, "image image.svg"), + ); + + const response = await req.get("/image image.svg"); + + expect(response.statusCode).toEqual(200); + expect(response.headers["content-length"]).toEqual( + fileData.byteLength.toString(), + ); + expect(response.headers["content-type"]).toEqual("image/svg+xml"); + }); + + it('should return the "404" code for the "GET" request to the "%FF" file', async () => { + const response = await req.get("/%FF"); + + expect(response.statusCode).toEqual(404); + expect(response.headers["content-type"]).toEqual( + "text/html; charset=utf-8", + ); + }); }); describe('should not work with the broken "publicPath" option', () => { @@ -2032,7 +2059,7 @@ describe.each([ expect(response.statusCode).toEqual(500); expect(response.headers["content-type"]).toEqual( - "text/html; charset=UTF-8", + "text/html; charset=utf-8", ); expect(response.text).toEqual( "\n" + @@ -2113,7 +2140,7 @@ describe.each([ expect(response.statusCode).toEqual(404); expect(response.headers["content-type"]).toEqual( - "text/html; charset=UTF-8", + "text/html; charset=utf-8", ); expect(response.text).toEqual( "\n" + @@ -2575,6 +2602,7 @@ describe.each([ output: { filename: "bundle.js", path: path.resolve(__dirname, "./outputs/write-to-disk-true"), + publicPath: "/public/", }, }); @@ -2598,7 +2626,7 @@ describe.each([ it("should find the bundle file on disk", (done) => { request(app) - .get("/bundle.js") + .get("/public/bundle.js") .expect(200, (error) => { if (error) { return done(error); @@ -2632,6 +2660,25 @@ describe.each([ ); }); }); + + it("should not allow to get files above root", async () => { + const response = await req.get("/public/..%2f../middleware.test.js"); + + expect(response.statusCode).toEqual(403); + expect(response.headers["content-type"]).toEqual( + "text/html; charset=utf-8", + ); + expect(response.text).toEqual(` + + + +Error + + +
Forbidden
+ +`); + }); }); describe('should work with "true" value when the `output.clean` is `true`', () => { diff --git a/types/utils/getFilenameFromUrl.d.ts b/types/utils/getFilenameFromUrl.d.ts index 7dfc8035d..0ab19b393 100644 --- a/types/utils/getFilenameFromUrl.d.ts +++ b/types/utils/getFilenameFromUrl.d.ts @@ -1,9 +1,5 @@ /// export = getFilenameFromUrl; -/** - * @typedef {Object} Extra - * @property {import("fs").Stats=} stats - */ /** * @template {IncomingMessage} Request * @template {ServerResponse} Response @@ -25,6 +21,7 @@ declare namespace getFilenameFromUrl { } type Extra = { stats?: import("fs").Stats | undefined; + errorCode?: number | undefined; }; type IncomingMessage = import("../index.js").IncomingMessage; type ServerResponse = import("../index.js").ServerResponse; From 145f03c376a379a9c8746d0a0d12003e7438138e Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 21:06:42 +0300 Subject: [PATCH 02/14] test: more --- test/middleware.test.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/middleware.test.js b/test/middleware.test.js index 6f5ad0ccb..45a59baf5 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -19,7 +19,7 @@ import webpackQueryStringConfig from "./fixtures/webpack.querystring.config"; import webpackClientServerConfig from "./fixtures/webpack.client.server.config"; // Suppress unnecessary stats output -global.console.log = jest.fn(); +// global.console.log = jest.fn(); describe.each([ ["express", express], @@ -187,6 +187,36 @@ describe.each([ expect(response.headers["content-type"]).toEqual("image/svg+xml"); }); + it('should return the "200" code for the "GET" request to the "image.svg" file with "/../"', async () => { + const fileData = instance.context.outputFileSystem.readFileSync( + path.resolve(outputPath, "image.svg"), + ); + + const response = await req.get("/public/../image.svg"); + + expect(response.statusCode).toEqual(200); + expect(response.headers["content-length"]).toEqual( + fileData.byteLength.toString(), + ); + expect(response.headers["content-type"]).toEqual("image/svg+xml"); + }); + + it('should return the "200" code for the "GET" request to the "image.svg" file with "/../../../"', async () => { + const fileData = instance.context.outputFileSystem.readFileSync( + path.resolve(outputPath, "image.svg"), + ); + + const response = await req.get( + "/public/assets/images/../../../image.svg", + ); + + expect(response.statusCode).toEqual(200); + expect(response.headers["content-length"]).toEqual( + fileData.byteLength.toString(), + ); + expect(response.headers["content-type"]).toEqual("image/svg+xml"); + }); + it('should return the "200" code for the "GET" request to the directory', async () => { const fileData = fs.readFileSync( path.resolve(__dirname, "./fixtures/index.html"), From c8447f7d2bfba11b4998a1c5e760bb0812af3472 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 21:19:51 +0300 Subject: [PATCH 03/14] fix: logic --- src/utils/getFilenameFromUrl.js | 23 +++++++++++++---------- test/middleware.test.js | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/utils/getFilenameFromUrl.js b/src/utils/getFilenameFromUrl.js index a5a7c64af..9c705a8cc 100644 --- a/src/utils/getFilenameFromUrl.js +++ b/src/utils/getFilenameFromUrl.js @@ -102,17 +102,17 @@ function getFilenameFromUrl(context, url, extra = {}) { continue; } - let pathname = decode(urlObject.pathname); + const pathname = decode(urlObject.pathname); - // Null byte(s) - if (pathname.includes("\0")) { - // eslint-disable-next-line no-param-reassign - extra.errorCode = 400; + if (pathname && pathname.startsWith(publicPathObject.pathname)) { + // Null byte(s) + if (pathname.includes("\0")) { + // eslint-disable-next-line no-param-reassign + extra.errorCode = 400; - return; - } + return; + } - if (pathname && pathname.startsWith(publicPathObject.pathname)) { // ".." is malicious if (UP_PATH_REGEXP.test(path.normalize(`.${path.sep}${pathname}`))) { // eslint-disable-next-line no-param-reassign @@ -123,8 +123,11 @@ function getFilenameFromUrl(context, url, extra = {}) { // Strip the `pathname` property from the `publicPath` option from the start of requested url // `/complex/foo.js` => `foo.js` - pathname = pathname.slice(publicPathObject.pathname.length); - filename = path.join(outputPath, pathname); + // and add outputPath + // `foo.js` => `/home/user/my-project/dist/foo.js` + filename = path.normalize( + path.join(outputPath, pathname.slice(publicPathObject.pathname.length)), + ); try { // eslint-disable-next-line no-param-reassign diff --git a/test/middleware.test.js b/test/middleware.test.js index 45a59baf5..79288d0b5 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -19,7 +19,7 @@ import webpackQueryStringConfig from "./fixtures/webpack.querystring.config"; import webpackClientServerConfig from "./fixtures/webpack.client.server.config"; // Suppress unnecessary stats output -// global.console.log = jest.fn(); +global.console.log = jest.fn(); describe.each([ ["express", express], From f8dd4b76c4609e4abd2119ef32db5578d1257f31 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 21:25:04 +0300 Subject: [PATCH 04/14] test: debug --- src/utils/getFilenameFromUrl.js | 26 ++++++++++++++++---------- test/middleware.test.js | 4 ++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/utils/getFilenameFromUrl.js b/src/utils/getFilenameFromUrl.js index 9c705a8cc..6e65150da 100644 --- a/src/utils/getFilenameFromUrl.js +++ b/src/utils/getFilenameFromUrl.js @@ -104,17 +104,20 @@ function getFilenameFromUrl(context, url, extra = {}) { const pathname = decode(urlObject.pathname); - if (pathname && pathname.startsWith(publicPathObject.pathname)) { - // Null byte(s) - if (pathname.includes("\0")) { - // eslint-disable-next-line no-param-reassign - extra.errorCode = 400; + // Null byte(s) + if (pathname.includes("\0")) { + // eslint-disable-next-line no-param-reassign + extra.errorCode = 400; - return; - } + return; + } + + if (pathname && pathname.startsWith(publicPathObject.pathname)) { + console.log("Pathname", pathname); + console.log("Normalize pathname", path.normalize(`./${pathname}`)); // ".." is malicious - if (UP_PATH_REGEXP.test(path.normalize(`.${path.sep}${pathname}`))) { + if (UP_PATH_REGEXP.test(path.normalize(`./${pathname}`))) { // eslint-disable-next-line no-param-reassign extra.errorCode = 403; @@ -125,10 +128,13 @@ function getFilenameFromUrl(context, url, extra = {}) { // `/complex/foo.js` => `foo.js` // and add outputPath // `foo.js` => `/home/user/my-project/dist/foo.js` - filename = path.normalize( - path.join(outputPath, pathname.slice(publicPathObject.pathname.length)), + filename = path.join( + outputPath, + pathname.slice(publicPathObject.pathname.length), ); + console.log("Filename", filename); + try { // eslint-disable-next-line no-param-reassign extra.stats = diff --git a/test/middleware.test.js b/test/middleware.test.js index 79288d0b5..7fff23f7e 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -19,7 +19,7 @@ import webpackQueryStringConfig from "./fixtures/webpack.querystring.config"; import webpackClientServerConfig from "./fixtures/webpack.client.server.config"; // Suppress unnecessary stats output -global.console.log = jest.fn(); +// global.console.log = jest.fn(); describe.each([ ["express", express], @@ -638,7 +638,7 @@ describe.each([ }); }); - describe("should work with difference requests", () => { + describe.only("should work with difference requests", () => { const basicOutputPath = path.resolve(__dirname, "./outputs/basic"); const fixtures = [ { From 1baf30c11364cf4ccf95c2977b2d46e384bff5f5 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 21:35:22 +0300 Subject: [PATCH 05/14] test: debug --- test/middleware.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/middleware.test.js b/test/middleware.test.js index 7fff23f7e..50474fa75 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -641,7 +641,7 @@ describe.each([ describe.only("should work with difference requests", () => { const basicOutputPath = path.resolve(__dirname, "./outputs/basic"); const fixtures = [ - { + /* { urls: [ { value: "bundle.js", @@ -722,7 +722,7 @@ describe.each([ code: 200, }, ], - }, + },*/ { file: "/complex/foo.js", data: 'console.log("foo");', @@ -744,7 +744,7 @@ describe.each([ }, ], }, - { + /*{ file: "/complex/complex/foo.js", data: 'console.log("foo");', urls: [ @@ -858,7 +858,7 @@ describe.each([ code: 200, }, ], - }, + },*/ ]; const configurations = [ @@ -866,7 +866,7 @@ describe.each([ output: { path: basicOutputPath, publicPath: "" }, publicPathForRequest: "/", }, - { + /* { output: { path: path.join(basicOutputPath, "dist"), publicPath: "", @@ -933,12 +933,12 @@ describe.each([ publicPath: "//test.domain/", }, publicPathForRequest: "/", - }, + },*/ ]; const isWindows = process.platform === "win32"; - if (isWindows) { + /*if (isWindows) { fixtures.push( { file: "windows.txt", @@ -998,7 +998,7 @@ describe.each([ publicPathForRequest: "/my%20static/", }, ); - } + }*/ for (const configuration of configurations) { // eslint-disable-next-line no-loop-func From fa133c36262b1785b55057a6c4dce18a244ae47a Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 21:41:23 +0300 Subject: [PATCH 06/14] test: debug --- test/middleware.test.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/middleware.test.js b/test/middleware.test.js index 50474fa75..31d5f12c8 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -744,7 +744,7 @@ describe.each([ }, ], }, - /*{ + { file: "/complex/complex/foo.js", data: 'console.log("foo");', urls: [ @@ -755,6 +755,7 @@ describe.each([ }, ], }, + /* { file: "/föö.js", data: 'console.log("foo");', @@ -866,7 +867,7 @@ describe.each([ output: { path: basicOutputPath, publicPath: "" }, publicPathForRequest: "/", }, - /* { + { output: { path: path.join(basicOutputPath, "dist"), publicPath: "", @@ -933,12 +934,12 @@ describe.each([ publicPath: "//test.domain/", }, publicPathForRequest: "/", - },*/ + }, ]; const isWindows = process.platform === "win32"; - /*if (isWindows) { + if (isWindows) { fixtures.push( { file: "windows.txt", @@ -998,7 +999,7 @@ describe.each([ publicPathForRequest: "/my%20static/", }, ); - }*/ + } for (const configuration of configurations) { // eslint-disable-next-line no-loop-func From b0f671ec8f8062090a9b0aa660bba3a2ed19eb03 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 21:48:55 +0300 Subject: [PATCH 07/14] fix: windows logic --- src/utils/getFilenameFromUrl.js | 21 +++++++++------------ test/middleware.test.js | 12 ++++++------ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/utils/getFilenameFromUrl.js b/src/utils/getFilenameFromUrl.js index 6e65150da..dc6952107 100644 --- a/src/utils/getFilenameFromUrl.js +++ b/src/utils/getFilenameFromUrl.js @@ -102,19 +102,18 @@ function getFilenameFromUrl(context, url, extra = {}) { continue; } - const pathname = decode(urlObject.pathname); + let { pathname } = urlObject; - // Null byte(s) - if (pathname.includes("\0")) { - // eslint-disable-next-line no-param-reassign - extra.errorCode = 400; + if (pathname && pathname.startsWith(publicPathObject.pathname)) { + pathname = decode(pathname); - return; - } + // Null byte(s) + if (pathname.includes("\0")) { + // eslint-disable-next-line no-param-reassign + extra.errorCode = 400; - if (pathname && pathname.startsWith(publicPathObject.pathname)) { - console.log("Pathname", pathname); - console.log("Normalize pathname", path.normalize(`./${pathname}`)); + return; + } // ".." is malicious if (UP_PATH_REGEXP.test(path.normalize(`./${pathname}`))) { @@ -133,8 +132,6 @@ function getFilenameFromUrl(context, url, extra = {}) { pathname.slice(publicPathObject.pathname.length), ); - console.log("Filename", filename); - try { // eslint-disable-next-line no-param-reassign extra.stats = diff --git a/test/middleware.test.js b/test/middleware.test.js index 31d5f12c8..78edc5903 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -19,7 +19,7 @@ import webpackQueryStringConfig from "./fixtures/webpack.querystring.config"; import webpackClientServerConfig from "./fixtures/webpack.client.server.config"; // Suppress unnecessary stats output -// global.console.log = jest.fn(); +global.console.log = jest.fn(); describe.each([ ["express", express], @@ -638,10 +638,10 @@ describe.each([ }); }); - describe.only("should work with difference requests", () => { + describe("should work with difference requests", () => { const basicOutputPath = path.resolve(__dirname, "./outputs/basic"); const fixtures = [ - /* { + { urls: [ { value: "bundle.js", @@ -722,7 +722,7 @@ describe.each([ code: 200, }, ], - },*/ + }, { file: "/complex/foo.js", data: 'console.log("foo");', @@ -755,7 +755,7 @@ describe.each([ }, ], }, - /* + { file: "/föö.js", data: 'console.log("foo");', @@ -859,7 +859,7 @@ describe.each([ code: 200, }, ], - },*/ + }, ]; const configurations = [ From 887eaf822c0ee0bab7c1931c63dede1d16d4fee0 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 21:52:36 +0300 Subject: [PATCH 08/14] test: debug --- test/middleware.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/middleware.test.js b/test/middleware.test.js index 78edc5903..5fd782f8e 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -638,7 +638,7 @@ describe.each([ }); }); - describe("should work with difference requests", () => { + describe.only("should work with difference requests", () => { const basicOutputPath = path.resolve(__dirname, "./outputs/basic"); const fixtures = [ { @@ -937,7 +937,7 @@ describe.each([ }, ]; - const isWindows = process.platform === "win32"; + const isWindows = false; if (isWindows) { fixtures.push( From fb305ff460b373c87500b9678fb36e5297aa50d2 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 21:58:18 +0300 Subject: [PATCH 09/14] test: debug --- src/utils/getFilenameFromUrl.js | 3 +++ test/middleware.test.js | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/utils/getFilenameFromUrl.js b/src/utils/getFilenameFromUrl.js index dc6952107..c30cda63c 100644 --- a/src/utils/getFilenameFromUrl.js +++ b/src/utils/getFilenameFromUrl.js @@ -104,6 +104,9 @@ function getFilenameFromUrl(context, url, extra = {}) { let { pathname } = urlObject; + console.log("PATHNAME", pathname) + console.log("PUBLIC PATH", publicPathObject.pathname) + if (pathname && pathname.startsWith(publicPathObject.pathname)) { pathname = decode(pathname); diff --git a/test/middleware.test.js b/test/middleware.test.js index 5fd782f8e..6be203a90 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -19,7 +19,7 @@ import webpackQueryStringConfig from "./fixtures/webpack.querystring.config"; import webpackClientServerConfig from "./fixtures/webpack.client.server.config"; // Suppress unnecessary stats output -global.console.log = jest.fn(); +// global.console.log = jest.fn(); describe.each([ ["express", express], @@ -641,7 +641,7 @@ describe.each([ describe.only("should work with difference requests", () => { const basicOutputPath = path.resolve(__dirname, "./outputs/basic"); const fixtures = [ - { + /*{ urls: [ { value: "bundle.js", @@ -722,7 +722,7 @@ describe.each([ code: 200, }, ], - }, + },*/ { file: "/complex/foo.js", data: 'console.log("foo");', @@ -744,7 +744,7 @@ describe.each([ }, ], }, - { + /*{ file: "/complex/complex/foo.js", data: 'console.log("foo");', urls: [ @@ -859,11 +859,11 @@ describe.each([ code: 200, }, ], - }, + },*/ ]; const configurations = [ - { + /* { output: { path: basicOutputPath, publicPath: "" }, publicPathForRequest: "/", }, @@ -934,13 +934,13 @@ describe.each([ publicPath: "//test.domain/", }, publicPathForRequest: "/", - }, + },*/ ]; - const isWindows = false; + const isWindows = process.platform === "win32"; if (isWindows) { - fixtures.push( + /*fixtures.push( { file: "windows.txt", data: "windows.txt content", @@ -974,7 +974,7 @@ describe.each([ }, ], }, - ); + );*/ configurations.push( { From e114a950d19b84587d0b50fa8e7def6d2aca5c42 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 22:09:18 +0300 Subject: [PATCH 10/14] test: debug --- test/middleware.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/middleware.test.js b/test/middleware.test.js index 6be203a90..3761de176 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -727,7 +727,7 @@ describe.each([ file: "/complex/foo.js", data: 'console.log("foo");', urls: [ - { + /*{ value: "complex/foo.js", contentType: "application/javascript; charset=utf-8", code: 200, @@ -736,7 +736,7 @@ describe.each([ value: "complex/./foo.js", contentType: "application/javascript; charset=utf-8", code: 200, - }, + },*/ { value: "complex/foo/../foo.js", contentType: "application/javascript; charset=utf-8", @@ -984,7 +984,7 @@ describe.each([ }, publicPathForRequest: "/static/", }, - { + /* { output: { path: path.join(basicOutputPath, "my%20static"), publicPath: "/static/", @@ -997,7 +997,7 @@ describe.each([ publicPath: "/my%20static/", }, publicPathForRequest: "/my%20static/", - }, + },*/ ); } From fb0dcc23968b5611cb2eaddbeb436b9bb1a0877c Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 22:11:46 +0300 Subject: [PATCH 11/14] test: debug --- test/middleware.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/middleware.test.js b/test/middleware.test.js index 3761de176..b5590fcad 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -977,27 +977,27 @@ describe.each([ );*/ configurations.push( - { + /* { output: { path: path.join(basicOutputPath, "my static"), publicPath: "/static/", }, publicPathForRequest: "/static/", }, - /* { + { output: { path: path.join(basicOutputPath, "my%20static"), publicPath: "/static/", }, publicPathForRequest: "/static/", - }, + },*/ { output: { path: path.join(basicOutputPath, "my %20 static"), publicPath: "/my%20static/", }, publicPathForRequest: "/my%20static/", - },*/ + }, ); } From d0edf11b041c554b158e4ebecf29a58785a26335 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 22:14:26 +0300 Subject: [PATCH 12/14] test: debug --- src/utils/getFilenameFromUrl.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/utils/getFilenameFromUrl.js b/src/utils/getFilenameFromUrl.js index c30cda63c..f3eb9e096 100644 --- a/src/utils/getFilenameFromUrl.js +++ b/src/utils/getFilenameFromUrl.js @@ -104,12 +104,11 @@ function getFilenameFromUrl(context, url, extra = {}) { let { pathname } = urlObject; - console.log("PATHNAME", pathname) - console.log("PUBLIC PATH", publicPathObject.pathname) - if (pathname && pathname.startsWith(publicPathObject.pathname)) { pathname = decode(pathname); + console.log("DECODED PATHNAME", pathname); + // Null byte(s) if (pathname.includes("\0")) { // eslint-disable-next-line no-param-reassign @@ -135,6 +134,8 @@ function getFilenameFromUrl(context, url, extra = {}) { pathname.slice(publicPathObject.pathname.length), ); + console.log("Filename", filename); + try { // eslint-disable-next-line no-param-reassign extra.stats = @@ -145,6 +146,8 @@ function getFilenameFromUrl(context, url, extra = {}) { continue; } + console.log("Stats", extra.stats); + if (extra.stats.isFile()) { foundFilename = filename; From 4019ebf39c3f41e750c14f6da14d7408450099eb Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 22:19:30 +0300 Subject: [PATCH 13/14] fix: windows logic again --- src/utils/getFilenameFromUrl.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/utils/getFilenameFromUrl.js b/src/utils/getFilenameFromUrl.js index f3eb9e096..70121eef7 100644 --- a/src/utils/getFilenameFromUrl.js +++ b/src/utils/getFilenameFromUrl.js @@ -102,13 +102,10 @@ function getFilenameFromUrl(context, url, extra = {}) { continue; } - let { pathname } = urlObject; - - if (pathname && pathname.startsWith(publicPathObject.pathname)) { - pathname = decode(pathname); - - console.log("DECODED PATHNAME", pathname); + const pathname = decode(urlObject.pathname); + const publicPathPathname = decode(publicPathObject.pathname); + if (pathname && pathname.startsWith(publicPathPathname)) { // Null byte(s) if (pathname.includes("\0")) { // eslint-disable-next-line no-param-reassign @@ -131,7 +128,7 @@ function getFilenameFromUrl(context, url, extra = {}) { // `foo.js` => `/home/user/my-project/dist/foo.js` filename = path.join( outputPath, - pathname.slice(publicPathObject.pathname.length), + pathname.slice(publicPathPathname.length), ); console.log("Filename", filename); From a7a8cf3c7352774e9818aab40df1a439da3894e3 Mon Sep 17 00:00:00 2001 From: "alexander.akait" Date: Tue, 19 Mar 2024 22:23:17 +0300 Subject: [PATCH 14/14] test: update --- src/utils/getFilenameFromUrl.js | 4 ---- test/middleware.test.js | 29 ++++++++++++++--------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/utils/getFilenameFromUrl.js b/src/utils/getFilenameFromUrl.js index 70121eef7..82a58115a 100644 --- a/src/utils/getFilenameFromUrl.js +++ b/src/utils/getFilenameFromUrl.js @@ -131,8 +131,6 @@ function getFilenameFromUrl(context, url, extra = {}) { pathname.slice(publicPathPathname.length), ); - console.log("Filename", filename); - try { // eslint-disable-next-line no-param-reassign extra.stats = @@ -143,8 +141,6 @@ function getFilenameFromUrl(context, url, extra = {}) { continue; } - console.log("Stats", extra.stats); - if (extra.stats.isFile()) { foundFilename = filename; diff --git a/test/middleware.test.js b/test/middleware.test.js index b5590fcad..79288d0b5 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -19,7 +19,7 @@ import webpackQueryStringConfig from "./fixtures/webpack.querystring.config"; import webpackClientServerConfig from "./fixtures/webpack.client.server.config"; // Suppress unnecessary stats output -// global.console.log = jest.fn(); +global.console.log = jest.fn(); describe.each([ ["express", express], @@ -638,10 +638,10 @@ describe.each([ }); }); - describe.only("should work with difference requests", () => { + describe("should work with difference requests", () => { const basicOutputPath = path.resolve(__dirname, "./outputs/basic"); const fixtures = [ - /*{ + { urls: [ { value: "bundle.js", @@ -722,12 +722,12 @@ describe.each([ code: 200, }, ], - },*/ + }, { file: "/complex/foo.js", data: 'console.log("foo");', urls: [ - /*{ + { value: "complex/foo.js", contentType: "application/javascript; charset=utf-8", code: 200, @@ -736,7 +736,7 @@ describe.each([ value: "complex/./foo.js", contentType: "application/javascript; charset=utf-8", code: 200, - },*/ + }, { value: "complex/foo/../foo.js", contentType: "application/javascript; charset=utf-8", @@ -744,7 +744,7 @@ describe.each([ }, ], }, - /*{ + { file: "/complex/complex/foo.js", data: 'console.log("foo");', urls: [ @@ -755,7 +755,6 @@ describe.each([ }, ], }, - { file: "/föö.js", data: 'console.log("foo");', @@ -859,11 +858,11 @@ describe.each([ code: 200, }, ], - },*/ + }, ]; const configurations = [ - /* { + { output: { path: basicOutputPath, publicPath: "" }, publicPathForRequest: "/", }, @@ -934,13 +933,13 @@ describe.each([ publicPath: "//test.domain/", }, publicPathForRequest: "/", - },*/ + }, ]; const isWindows = process.platform === "win32"; if (isWindows) { - /*fixtures.push( + fixtures.push( { file: "windows.txt", data: "windows.txt content", @@ -974,10 +973,10 @@ describe.each([ }, ], }, - );*/ + ); configurations.push( - /* { + { output: { path: path.join(basicOutputPath, "my static"), publicPath: "/static/", @@ -990,7 +989,7 @@ describe.each([ publicPath: "/static/", }, publicPathForRequest: "/static/", - },*/ + }, { output: { path: path.join(basicOutputPath, "my %20 static"),