fix(packagist): Replace V2 URL path instead of joining it (#20709)

This commit is contained in:
Sergei Zharinov 2023-03-02 17:19:55 +03:00 committed by GitHub
parent c5edc5d54e
commit cd06651f89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 53 deletions

View file

@ -5,7 +5,7 @@ import { cache } from '../../../util/cache/package/decorator';
import * as hostRules from '../../../util/host-rules'; import * as hostRules from '../../../util/host-rules';
import type { HttpOptions } from '../../../util/http/types'; import type { HttpOptions } from '../../../util/http/types';
import * as p from '../../../util/promises'; import * as p from '../../../util/promises';
import { parseUrl, resolveBaseUrl } from '../../../util/url'; import { replaceUrlPath, resolveBaseUrl } from '../../../util/url';
import * as composerVersioning from '../../versioning/composer'; import * as composerVersioning from '../../versioning/composer';
import { Datasource } from '../datasource'; import { Datasource } from '../datasource';
import type { GetReleasesConfig, ReleaseResult } from '../types'; import type { GetReleasesConfig, ReleaseResult } from '../types';
@ -70,7 +70,7 @@ export class PackagistDatasource extends Datasource {
): string { ): string {
const { key, hash } = regFile; const { key, hash } = regFile;
const fileName = hash ? key.replace('%hash%', hash) : key; const fileName = hash ? key.replace('%hash%', hash) : key;
const url = `${regUrl}/${fileName}`; const url = resolveBaseUrl(regUrl, fileName);
return url; return url;
} }
@ -124,12 +124,16 @@ export class PackagistDatasource extends Datasource {
metadataUrl: string, metadataUrl: string,
packageName: string packageName: string
): Promise<ReleaseResult | null> { ): Promise<ReleaseResult | null> {
let pkgUrl = metadataUrl.replace('%package%', packageName); const pkgUrl = replaceUrlPath(
pkgUrl = parseUrl(pkgUrl) ? pkgUrl : resolveBaseUrl(registryUrl, pkgUrl); registryUrl,
metadataUrl.replace('%package%', packageName)
);
const pkgPromise = this.getJson(pkgUrl, z.unknown()); const pkgPromise = this.getJson(pkgUrl, z.unknown());
let devUrl = metadataUrl.replace('%package%', `${packageName}~dev`); const devUrl = replaceUrlPath(
devUrl = parseUrl(devUrl) ? devUrl : resolveBaseUrl(registryUrl, devUrl); registryUrl,
metadataUrl.replace('%package%', `${packageName}~dev`)
);
const devPromise = this.getJson(devUrl, z.unknown()).then( const devPromise = this.getJson(devUrl, z.unknown()).then(
(x) => x, (x) => x,
() => null () => null
@ -144,8 +148,6 @@ export class PackagistDatasource extends Datasource {
registryUrl: string, registryUrl: string,
registryMeta: RegistryMeta registryMeta: RegistryMeta
): string | null { ): string | null {
const { origin: registryHost } = new URL(registryUrl);
if ( if (
registryMeta.providersUrl && registryMeta.providersUrl &&
packageName in registryMeta.providerPackages packageName in registryMeta.providerPackages
@ -155,12 +157,12 @@ export class PackagistDatasource extends Datasource {
if (hash) { if (hash) {
url = url.replace('%hash%', hash); url = url.replace('%hash%', hash);
} }
return resolveBaseUrl(registryHost, url); return replaceUrlPath(registryUrl, url);
} }
if (registryMeta.providersLazyUrl) { if (registryMeta.providersLazyUrl) {
return resolveBaseUrl( return replaceUrlPath(
registryHost, registryUrl,
registryMeta.providersLazyUrl.replace('%package%', packageName) registryMeta.providersLazyUrl.replace('%package%', packageName)
); );
} }

View file

@ -6,57 +6,91 @@ import {
joinUrlParts, joinUrlParts,
parseLinkHeader, parseLinkHeader,
parseUrl, parseUrl,
replaceUrlPath,
resolveBaseUrl, resolveBaseUrl,
trimTrailingSlash, trimTrailingSlash,
validateUrl, validateUrl,
} from './url'; } from './url';
describe('util/url', () => { describe('util/url', () => {
test.each([ test.each`
['http://foo.io', '', 'http://foo.io'], baseUrl | x | result
['http://foo.io/', '', 'http://foo.io'], ${'http://foo.io'} | ${''} | ${'http://foo.io'}
['http://foo.io', '/', 'http://foo.io/'], ${'http://foo.io/'} | ${''} | ${'http://foo.io'}
['http://foo.io/', '/', 'http://foo.io/'], ${'http://foo.io'} | ${'/'} | ${'http://foo.io/'}
${'http://foo.io/'} | ${'/'} | ${'http://foo.io/'}
['http://foo.io', '/aaa', 'http://foo.io/aaa'], ${'http://foo.io'} | ${'/aaa'} | ${'http://foo.io/aaa'}
['http://foo.io', 'aaa', 'http://foo.io/aaa'], ${'http://foo.io'} | ${'aaa'} | ${'http://foo.io/aaa'}
['http://foo.io/', '/aaa', 'http://foo.io/aaa'], ${'http://foo.io/'} | ${'/aaa'} | ${'http://foo.io/aaa'}
['http://foo.io/', 'aaa', 'http://foo.io/aaa'], ${'http://foo.io/'} | ${'aaa'} | ${'http://foo.io/aaa'}
['http://foo.io', '/aaa/', 'http://foo.io/aaa/'], ${'http://foo.io'} | ${'/aaa/'} | ${'http://foo.io/aaa/'}
['http://foo.io', 'aaa/', 'http://foo.io/aaa/'], ${'http://foo.io'} | ${'aaa/'} | ${'http://foo.io/aaa/'}
['http://foo.io/', '/aaa/', 'http://foo.io/aaa/'], ${'http://foo.io/'} | ${'/aaa/'} | ${'http://foo.io/aaa/'}
['http://foo.io/', 'aaa/', 'http://foo.io/aaa/'], ${'http://foo.io/'} | ${'aaa/'} | ${'http://foo.io/aaa/'}
${'http://foo.io/aaa'} | ${'/bbb'} | ${'http://foo.io/aaa/bbb'}
['http://foo.io/aaa', '/bbb', 'http://foo.io/aaa/bbb'], ${'http://foo.io/aaa'} | ${'bbb'} | ${'http://foo.io/aaa/bbb'}
['http://foo.io/aaa', 'bbb', 'http://foo.io/aaa/bbb'], ${'http://foo.io/aaa/'} | ${'/bbb'} | ${'http://foo.io/aaa/bbb'}
['http://foo.io/aaa/', '/bbb', 'http://foo.io/aaa/bbb'], ${'http://foo.io/aaa/'} | ${'bbb'} | ${'http://foo.io/aaa/bbb'}
['http://foo.io/aaa/', 'bbb', 'http://foo.io/aaa/bbb'], ${'http://foo.io/aaa'} | ${'/bbb/'} | ${'http://foo.io/aaa/bbb/'}
${'http://foo.io/aaa'} | ${'bbb/'} | ${'http://foo.io/aaa/bbb/'}
['http://foo.io/aaa', '/bbb/', 'http://foo.io/aaa/bbb/'], ${'http://foo.io/aaa/'} | ${'/bbb/'} | ${'http://foo.io/aaa/bbb/'}
['http://foo.io/aaa', 'bbb/', 'http://foo.io/aaa/bbb/'], ${'http://foo.io/aaa/'} | ${'bbb/'} | ${'http://foo.io/aaa/bbb/'}
['http://foo.io/aaa/', '/bbb/', 'http://foo.io/aaa/bbb/'], ${'http://foo.io'} | ${'http://bar.io/bbb'} | ${'http://bar.io/bbb'}
['http://foo.io/aaa/', 'bbb/', 'http://foo.io/aaa/bbb/'], ${'http://foo.io/'} | ${'http://bar.io/bbb'} | ${'http://bar.io/bbb'}
${'http://foo.io/aaa'} | ${'http://bar.io/bbb'} | ${'http://bar.io/bbb'}
['http://foo.io', 'http://bar.io/bbb', 'http://bar.io/bbb'], ${'http://foo.io/aaa/'} | ${'http://bar.io/bbb'} | ${'http://bar.io/bbb'}
['http://foo.io/', 'http://bar.io/bbb', 'http://bar.io/bbb'], ${'http://foo.io'} | ${'http://bar.io/bbb/'} | ${'http://bar.io/bbb/'}
['http://foo.io/aaa', 'http://bar.io/bbb', 'http://bar.io/bbb'], ${'http://foo.io/'} | ${'http://bar.io/bbb/'} | ${'http://bar.io/bbb/'}
['http://foo.io/aaa/', 'http://bar.io/bbb', 'http://bar.io/bbb'], ${'http://foo.io/aaa'} | ${'http://bar.io/bbb/'} | ${'http://bar.io/bbb/'}
${'http://foo.io/aaa/'} | ${'http://bar.io/bbb/'} | ${'http://bar.io/bbb/'}
['http://foo.io', 'http://bar.io/bbb/', 'http://bar.io/bbb/'], ${'http://foo.io'} | ${'aaa?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
['http://foo.io/', 'http://bar.io/bbb/', 'http://bar.io/bbb/'], ${'http://foo.io'} | ${'/aaa?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
['http://foo.io/aaa', 'http://bar.io/bbb/', 'http://bar.io/bbb/'], ${'http://foo.io/'} | ${'aaa?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
['http://foo.io/aaa/', 'http://bar.io/bbb/', 'http://bar.io/bbb/'], ${'http://foo.io/'} | ${'/aaa?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
${'http://foo.io'} | ${'aaa/?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
['http://foo.io', 'aaa?bbb=z', 'http://foo.io/aaa?bbb=z'], `('$baseUrl + $x => $result', ({ baseUrl, x, result }) => {
['http://foo.io', '/aaa?bbb=z', 'http://foo.io/aaa?bbb=z'],
['http://foo.io/', 'aaa?bbb=z', 'http://foo.io/aaa?bbb=z'],
['http://foo.io/', '/aaa?bbb=z', 'http://foo.io/aaa?bbb=z'],
['http://foo.io', 'aaa/?bbb=z', 'http://foo.io/aaa?bbb=z'],
])('%s + %s => %s', (baseUrl, x, result) => {
expect(resolveBaseUrl(baseUrl, x)).toBe(result); expect(resolveBaseUrl(baseUrl, x)).toBe(result);
}); });
test.each`
baseUrl | x | result
${'http://foo.io'} | ${''} | ${'http://foo.io'}
${'http://foo.io/'} | ${''} | ${'http://foo.io'}
${'http://foo.io'} | ${'/'} | ${'http://foo.io/'}
${'http://foo.io/'} | ${'/'} | ${'http://foo.io/'}
${'http://foo.io'} | ${'/aaa'} | ${'http://foo.io/aaa'}
${'http://foo.io'} | ${'aaa'} | ${'http://foo.io/aaa'}
${'http://foo.io/'} | ${'/aaa'} | ${'http://foo.io/aaa'}
${'http://foo.io/'} | ${'aaa'} | ${'http://foo.io/aaa'}
${'http://foo.io'} | ${'/aaa/'} | ${'http://foo.io/aaa/'}
${'http://foo.io'} | ${'aaa/'} | ${'http://foo.io/aaa/'}
${'http://foo.io/'} | ${'/aaa/'} | ${'http://foo.io/aaa/'}
${'http://foo.io/'} | ${'aaa/'} | ${'http://foo.io/aaa/'}
${'http://foo.io/aaa'} | ${'/bbb'} | ${'http://foo.io/bbb'}
${'http://foo.io/aaa'} | ${'bbb'} | ${'http://foo.io/bbb'}
${'http://foo.io/aaa/'} | ${'/bbb'} | ${'http://foo.io/bbb'}
${'http://foo.io/aaa/'} | ${'bbb'} | ${'http://foo.io/bbb'}
${'http://foo.io/aaa'} | ${'/bbb/'} | ${'http://foo.io/bbb/'}
${'http://foo.io/aaa'} | ${'bbb/'} | ${'http://foo.io/bbb/'}
${'http://foo.io/aaa/'} | ${'/bbb/'} | ${'http://foo.io/bbb/'}
${'http://foo.io/aaa/'} | ${'bbb/'} | ${'http://foo.io/bbb/'}
${'http://foo.io'} | ${'http://bar.io/bbb'} | ${'http://bar.io/bbb'}
${'http://foo.io/'} | ${'http://bar.io/bbb'} | ${'http://bar.io/bbb'}
${'http://foo.io/aaa'} | ${'http://bar.io/bbb'} | ${'http://bar.io/bbb'}
${'http://foo.io/aaa/'} | ${'http://bar.io/bbb'} | ${'http://bar.io/bbb'}
${'http://foo.io'} | ${'http://bar.io/bbb/'} | ${'http://bar.io/bbb/'}
${'http://foo.io/'} | ${'http://bar.io/bbb/'} | ${'http://bar.io/bbb/'}
${'http://foo.io/aaa'} | ${'http://bar.io/bbb/'} | ${'http://bar.io/bbb/'}
${'http://foo.io/aaa/'} | ${'http://bar.io/bbb/'} | ${'http://bar.io/bbb/'}
${'http://foo.io'} | ${'aaa?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
${'http://foo.io'} | ${'/aaa?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
${'http://foo.io/'} | ${'aaa?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
${'http://foo.io/'} | ${'/aaa?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
${'http://foo.io'} | ${'aaa/?bbb=z'} | ${'http://foo.io/aaa?bbb=z'}
`('replaceUrlPath("$baseUrl", "$x") => $result', ({ baseUrl, x, result }) => {
expect(replaceUrlPath(baseUrl, x)).toBe(result);
});
it('getQueryString', () => { it('getQueryString', () => {
expect(getQueryString({ a: 1, b: [1, 2] })).toBe('a=1&b=1&b=2'); expect(getQueryString({ a: 1, b: [1, 2] })).toBe('a=1&b=1&b=2');
}); });

View file

@ -30,6 +30,12 @@ export function trimLeadingSlash(path: string): string {
return path.replace(/^\/+/, ''); return path.replace(/^\/+/, '');
} }
/**
* Resolves an input path against a base URL
*
* @param baseUrl - base URL to resolve against
* @param input - input path (if this is a full URL, it will be returned)
*/
export function resolveBaseUrl(baseUrl: string, input: string | URL): string { export function resolveBaseUrl(baseUrl: string, input: string | URL): string {
const inputString = input.toString(); const inputString = input.toString();
@ -44,6 +50,21 @@ export function resolveBaseUrl(baseUrl: string, input: string | URL): string {
return host ? inputString : urlJoin(baseUrl, pathname || ''); return host ? inputString : urlJoin(baseUrl, pathname || '');
} }
/**
* Replaces the path of a URL with a new path
*
* @param baseUrl - source URL
* @param path - replacement path (if this is a full URL, it will be returned)
*/
export function replaceUrlPath(baseUrl: string | URL, path: string): string {
if (parseUrl(path)) {
return path;
}
const { origin } = is.string(baseUrl) ? new URL(baseUrl) : baseUrl;
return urlJoin(origin, path);
}
export function getQueryString(params: Record<string, any>): string { export function getQueryString(params: Record<string, any>): string {
const usp = new URLSearchParams(); const usp = new URLSearchParams();
for (const [k, v] of Object.entries(params)) { for (const [k, v] of Object.entries(params)) {