diff --git a/__tests__/auth.test.ts b/__tests__/auth.test.ts index 4500de15..a18693df 100644 --- a/__tests__/auth.test.ts +++ b/__tests__/auth.test.ts @@ -913,9 +913,28 @@ describe('auth tests', () => { ]); }); + it.each([ + 'https://repo.example.com:8443', + 'https://repo.example.com:8443/maven', + 'https://[2001:db8::1]', + 'https://[2001:db8::1]/maven', + 'https://[2001:db8::1]:8443', + 'https://[2001:db8::1]:8443/maven' + ])('preserves the complete mirror URL %s with an explicit selector', url => { + expect( + auth.parseMavenMirrors([`private:${url}:external:http:*,!internal`]) + ).toEqual([{id: 'private', url, mirrorOf: 'external:http:*,!internal'}]); + }); + it.each([ 'private', 'private:https://repo.example.com', + 'private:https://repo.example.com:8443', + 'private:https://repo.example.com:8443/maven', + 'private:https://[2001:db8::1]', + 'private:https://[2001:db8::1]/maven', + 'private:https://[2001:db8::1]:8443', + 'private:https://[2001:db8::1]:8443/maven', ':https://repo.example.com:*', 'private::*', 'private:https://repo.example.com:', diff --git a/dist/setup/81.index.js b/dist/setup/81.index.js index 28c50048..1dc4058b 100644 --- a/dist/setup/81.index.js +++ b/dist/setup/81.index.js @@ -234,15 +234,15 @@ function parseMavenMirrors(entries) { } const separator = entry.indexOf(':'); const remainder = entry.slice(separator + 1).trim(); - // Consume the URL scheme before looking for the selector delimiter. Selector - // tokens may themselves contain colons (external:* and external:http:*). - const scheme = /^[A-Za-z][A-Za-z0-9+.-]*:/.exec(remainder)?.[0] ?? ''; - const fields = /^(.*?):((?:\s*!?external:(?:http:)?\*\s*|[^:,]*)(?:,(?:\s*!?external:(?:http:)?\*\s*|[^:,]*))*)$/.exec(remainder.slice(scheme.length)); + // Protect scheme/authority colons (including IPv6 and ports) before + // finding the selector, which may itself contain external:http:*. + const urlPrefix = /^[A-Za-z][A-Za-z0-9+.-]*:(?:\/\/(?:[^/?#@\s]*@)?(?:\[[^\]]+\]|[^:/?#\s]+)(?::\d+(?=[/?#:\s]|$))?)?/.exec(remainder)?.[0] ?? ''; + const fields = /^(.*?):((?:\s*!?external:(?:http:)?\*\s*|[^:,]*)(?:,(?:\s*!?external:(?:http:)?\*\s*|[^:,]*))*)$/.exec(remainder.slice(urlPrefix.length)); if (separator < 0 || !fields) { throw new Error(`Invalid mvn-mirrors entry at line ${index + 1}. Expected format: mirror-id:mirror-url:mirror-of`); } const id = entry.slice(0, separator).trim(); - const url = `${scheme}${fields[1]}`.trim(); + const url = `${urlPrefix}${fields[1]}`.trim(); const mirrorOf = fields[2].trim(); if (!id || !url || !mirrorOf) { throw new Error(`Invalid mvn-mirrors entry at line ${index + 1}. mirror-id, mirror URL, and mirror-of are required`); diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 488d1efb..11799679 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -1099,8 +1099,9 @@ Unlike repository profiles, mirrors replace the destination Maven contacts. The Central controls described above do not redirect arbitrary POM repositories. Each nonblank line has the format `mirror-id:mirror-url:mirror-of`. IDs must be -unique within this input. URL schemes and ports, and colons in `mirrorOf` -selectors, are preserved. For example: +unique within this input. URL schemes, ports, IPv6 addresses, and colons in +`mirrorOf` selectors are preserved. An explicit `mirror-of` field is required +after the complete URL, including any port. For example: ```yaml steps: diff --git a/src/auth.ts b/src/auth.ts index c3c25112..473343d3 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -355,12 +355,15 @@ export function parseMavenMirrors(entries: string[]): MavenMirror[] { const separator = entry.indexOf(':'); const remainder = entry.slice(separator + 1).trim(); - // Consume the URL scheme before looking for the selector delimiter. Selector - // tokens may themselves contain colons (external:* and external:http:*). - const scheme = /^[A-Za-z][A-Za-z0-9+.-]*:/.exec(remainder)?.[0] ?? ''; + // Protect scheme/authority colons (including IPv6 and ports) before + // finding the selector, which may itself contain external:http:*. + const urlPrefix = + /^[A-Za-z][A-Za-z0-9+.-]*:(?:\/\/(?:[^/?#@\s]*@)?(?:\[[^\]]+\]|[^:/?#\s]+)(?::\d+(?=[/?#:\s]|$))?)?/.exec( + remainder + )?.[0] ?? ''; const fields = /^(.*?):((?:\s*!?external:(?:http:)?\*\s*|[^:,]*)(?:,(?:\s*!?external:(?:http:)?\*\s*|[^:,]*))*)$/.exec( - remainder.slice(scheme.length) + remainder.slice(urlPrefix.length) ); if (separator < 0 || !fields) { throw new Error( @@ -369,7 +372,7 @@ export function parseMavenMirrors(entries: string[]): MavenMirror[] { } const id = entry.slice(0, separator).trim(); - const url = `${scheme}${fields[1]}`.trim(); + const url = `${urlPrefix}${fields[1]}`.trim(); const mirrorOf = fields[2].trim(); if (!id || !url || !mirrorOf) { throw new Error(