Reject Maven mirrors missing selectors after URL authorities

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 59df028a-0d1b-49cb-8126-ab26408663e4
This commit is contained in:
Bruno Borges
2026-10-16 10:01:00 -04:00
parent df4305dee7
commit db92f3d58c
4 changed files with 35 additions and 12 deletions

View File

@@ -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:',

View File

@@ -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`);

View File

@@ -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:

View File

@@ -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(