From dfd469a77a1ffb5895c9e4b007ad9c3e3cc3ee66 Mon Sep 17 00:00:00 2001 From: David Vogel Date: Fri, 10 Oct 2025 16:07:05 -0400 Subject: [PATCH 1/2] fix(url): handle arbitrary number of levels for relative paths --- src/core/utils/url.js | 24 +++++++++++++++++++----- test/unit/core/utils.js | 3 +++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/core/utils/url.js b/src/core/utils/url.js index 4186b72909a..2f44b40d997 100644 --- a/src/core/utils/url.js +++ b/src/core/utils/url.js @@ -61,15 +61,30 @@ export function sanitizeUrl(url) { if (urlTrimmed.startsWith("/")) { return `${urlObject.pathname}${urlObject.search}${urlObject.hash}` } - + if (urlTrimmed.startsWith("./")) { return `.${urlObject.pathname}${urlObject.search}${urlObject.hash}` } - + + // Handle multiple levels of relative paths (../, ../../, ../../../, etc.) if (urlTrimmed.startsWith("../")) { - return `..${urlObject.pathname}${urlObject.search}${urlObject.hash}` + // Count the number of ../ segments + const segments = urlTrimmed.split("/") + let relativeLevels = 0 + + for (const segment of segments) { + if (segment === "..") { + relativeLevels++ + } else { + break + } + } + + // Reconstruct the relative path with correct number of ../ + const relativePath = "../".repeat(relativeLevels) + return `${relativePath}${urlObject.pathname.substring(1)}${urlObject.search}${urlObject.hash}` } - + return `${urlObject.pathname.substring(1)}${urlObject.search}${urlObject.hash}` } @@ -78,4 +93,3 @@ export function sanitizeUrl(url) { return blankURL } } - diff --git a/test/unit/core/utils.js b/test/unit/core/utils.js index effb7fd1f78..7eb318d91e9 100644 --- a/test/unit/core/utils.js +++ b/test/unit/core/utils.js @@ -1486,6 +1486,9 @@ describe("utils", () => { expect(sanitizeUrl("./openapi.json")).toEqual("./openapi.json") expect(sanitizeUrl("..openapi.json")).toEqual("..openapi.json") expect(sanitizeUrl("../openapi.json")).toEqual("../openapi.json") + expect(sanitizeUrl("../../openapi.json")).toEqual("../../openapi.json") + expect(sanitizeUrl("../../../openapi.json")).toEqual("../../../openapi.json") + expect(sanitizeUrl("../../../../openapi.json")).toEqual("../../../../openapi.json") }) it("should gracefully handle empty strings", () => { From c3bb466a25c5ebc268f39671d133d8a8772f8751 Mon Sep 17 00:00:00 2001 From: David Vogel Date: Fri, 10 Oct 2025 16:30:26 -0400 Subject: [PATCH 2/2] added fix for paths starting with single period --- src/core/utils/url.js | 31 ++++++++++++++++--------------- test/unit/core/utils.js | 1 + 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/core/utils/url.js b/src/core/utils/url.js index 2f44b40d997..57d5e0f606b 100644 --- a/src/core/utils/url.js +++ b/src/core/utils/url.js @@ -62,27 +62,28 @@ export function sanitizeUrl(url) { return `${urlObject.pathname}${urlObject.search}${urlObject.hash}` } - if (urlTrimmed.startsWith("./")) { - return `.${urlObject.pathname}${urlObject.search}${urlObject.hash}` - } - - // Handle multiple levels of relative paths (../, ../../, ../../../, etc.) - if (urlTrimmed.startsWith("../")) { - // Count the number of ../ segments + // Handle relative paths (./path, ../path, ./../../path, etc.) + if (urlTrimmed.startsWith("./") || urlTrimmed.startsWith("../")) { const segments = urlTrimmed.split("/") - let relativeLevels = 0 - - for (const segment of segments) { - if (segment === "..") { - relativeLevels++ + let relativePath = "" + let pathStartIndex = 0 + + // Process initial relative segments + for (let i = 0; i < segments.length; i++) { + if (segments[i] === ".") { + relativePath += "./" + pathStartIndex = i + 1 + } else if (segments[i] === "..") { + relativePath += "../" + pathStartIndex = i + 1 } else { break } } - // Reconstruct the relative path with correct number of ../ - const relativePath = "../".repeat(relativeLevels) - return `${relativePath}${urlObject.pathname.substring(1)}${urlObject.search}${urlObject.hash}` + // Get the remaining path from the URL object + const remainingPath = urlObject.pathname.substring(1) + return `${relativePath}${remainingPath}${urlObject.search}${urlObject.hash}` } return `${urlObject.pathname.substring(1)}${urlObject.search}${urlObject.hash}` diff --git a/test/unit/core/utils.js b/test/unit/core/utils.js index 7eb318d91e9..5787fd7de88 100644 --- a/test/unit/core/utils.js +++ b/test/unit/core/utils.js @@ -1489,6 +1489,7 @@ describe("utils", () => { expect(sanitizeUrl("../../openapi.json")).toEqual("../../openapi.json") expect(sanitizeUrl("../../../openapi.json")).toEqual("../../../openapi.json") expect(sanitizeUrl("../../../../openapi.json")).toEqual("../../../../openapi.json") + expect(sanitizeUrl("./../../../openapi.json")).toEqual("./../../../openapi.json") }) it("should gracefully handle empty strings", () => {