diff --git a/lib/util/identifier.js b/lib/util/identifier.js index d38cc39..ab3c2f8 100644 --- a/lib/util/identifier.js +++ b/lib/util/identifier.js @@ -7,19 +7,61 @@ const PATH_QUERY_FRAGMENT_REGEXP = /^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; +const ZERO_ESCAPE_REGEXP = /\0(.)/g; /** * @param {string} identifier identifier * @returns {[string, string, string]|null} parsed identifier */ function parseIdentifier(identifier) { + if (!identifier) { + return null; + } + + const firstEscape = identifier.indexOf("\0"); + if (firstEscape < 0) { + // Fast path for inputs that don't use \0 escaping. + const queryStart = identifier.indexOf("?"); + // Start at index 1 to ignore a possible leading hash. + const fragmentStart = identifier.indexOf("#", 1); + + if (fragmentStart < 0) { + if (queryStart < 0) { + // No fragment, no query + return [identifier, "", ""]; + } + // Query, no fragment + return [ + identifier.slice(0, queryStart), + identifier.slice(queryStart), + "" + ]; + } + + if (queryStart < 0 || fragmentStart < queryStart) { + // Fragment, no query + return [ + identifier.slice(0, fragmentStart), + "", + identifier.slice(fragmentStart) + ]; + } + + // Query and fragment + return [ + identifier.slice(0, queryStart), + identifier.slice(queryStart, fragmentStart), + identifier.slice(fragmentStart) + ]; + } + const match = PATH_QUERY_FRAGMENT_REGEXP.exec(identifier); if (!match) return null; return [ - match[1].replace(/\0(.)/g, "$1"), - match[2] ? match[2].replace(/\0(.)/g, "$1") : "", + match[1].replace(ZERO_ESCAPE_REGEXP, "$1"), + match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "", match[3] || "" ]; } diff --git a/test/identifier.test.js b/test/identifier.test.js index 6d18954..5ef3dca 100644 --- a/test/identifier.test.js +++ b/test/identifier.test.js @@ -53,6 +53,10 @@ describe("parse identifier. edge cases", () => { { input: "path/#/not/a/hash?not-a-query", expected: ["path/", "", "#/not/a/hash?not-a-query"] + }, + { + input: "#\0?\0#ab\0\0c?\0#\0\0query#?#\0fragment", + expected: ["#?#ab\0c", "?#\0query", "#?#\0fragment"] } ];