diff --git a/src/parse.ts b/src/parse.ts index b2a2e7e0..73019b76 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -72,7 +72,7 @@ export function parseURL(input = "", defaultProto?: string): ParsedURL { const [, protocol = "", auth, hostAndPath = ""] = input .replace(/\\/g, "/") - .match(/^[\s\0]*([\w+.-]{2,}:)?\/\/([^/@]+@)?(.*)/) || []; + .match(/^[\s\0]*([\w+.-]{2,}:)?\/\/([^/?#]*@)?(.*)/) || []; // eslint-disable-next-line prefer-const let [, host = "", path = ""] = hostAndPath.match(/([^#/?]*)(.*)?/) || []; diff --git a/test/parse.test.ts b/test/parse.test.ts index ba72667a..986c355d 100644 --- a/test/parse.test.ts +++ b/test/parse.test.ts @@ -294,3 +294,71 @@ describe("parseFilename", () => { }); } }); + +describe("parseURL authority boundaries", () => { + test.each([ + [ + "http://example.com?email=user@example.org", + "?email=user@example.org", + "", + ], + ["https://example.com#user@example.org", "", "#user@example.org"], + [ + "//example.com?email=user@example.org#fragment", + "?email=user@example.org", + "#fragment", + ], + ])("keeps @ after the authority boundary in %s", (input, search, hash) => { + expect(parseURL(input)).toMatchObject({ + protocol: input.startsWith("//") ? "" : input.split("//")[0], + auth: "", + host: "example.com", + pathname: "", + search, + hash, + }); + }); + + test("preserves actual user info before query and fragment", () => { + expect( + parseURL( + "https://user:pass@example.com?email=user@example.org#user@example.org", + ), + ).toMatchObject({ + protocol: "https:", + auth: "user:pass", + host: "example.com", + pathname: "", + search: "?email=user@example.org", + hash: "#user@example.org", + }); + }); +}); + +describe("parseURL user-info separator", () => { + test.each([ + ["https://user@team@example.com", "user@team", "", ""], + [ + "https://user@team@example.com?email=user@example.org", + "user@team", + "?email=user@example.org", + "", + ], + [ + "https://user@team@example.com#user@example.org", + "user@team", + "", + "#user@example.org", + ], + ["https://@example.com", "", "", ""], + ])("uses the last authority @ in %s", (input, auth, search, hash) => { + expect(parseURL(input)).toMatchObject({ + protocol: "https:", + auth, + host: "example.com", + pathname: "", + search, + hash, + }); + }); +});