renovate 44.222.5 → 44.222.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/util/url.d.ts +4 -0
- package/dist/util/url.js +8 -3
- package/dist/util/url.js.map +1 -1
- package/package.json +2 -2
- package/renovate-schema.json +2 -2
package/dist/util/url.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import _parseLinkHeader from "parse-link-header";
|
|
2
|
+
/**
|
|
3
|
+
* Joins URL parts with `url-join`. The result ends with a slash only if the last
|
|
4
|
+
* part does.
|
|
5
|
+
*/
|
|
2
6
|
export declare function joinUrlParts(...parts: string[]): string;
|
|
3
7
|
export declare function ensurePathPrefix(url: string, prefix: string): string;
|
|
4
8
|
export declare function ensureTrailingSlash(url: string): string;
|
package/dist/util/url.js
CHANGED
|
@@ -4,8 +4,13 @@ import { isArray, isNonEmptyString, isObject, isString, isUrlInstance } from "@s
|
|
|
4
4
|
import _parseLinkHeader from "parse-link-header";
|
|
5
5
|
import urlJoin from "url-join";
|
|
6
6
|
// lib/util/url.ts
|
|
7
|
+
/**
|
|
8
|
+
* Joins URL parts with `url-join`. The result ends with a slash only if the last
|
|
9
|
+
* part does.
|
|
10
|
+
*/
|
|
7
11
|
function joinUrlParts(...parts) {
|
|
8
|
-
|
|
12
|
+
const joined = urlJoin(...parts);
|
|
13
|
+
return parts.at(-1)?.endsWith("/") ? ensureTrailingSlash(joined) : trimTrailingSlash(joined);
|
|
9
14
|
}
|
|
10
15
|
function ensurePathPrefix(url, prefix) {
|
|
11
16
|
const parsed = new URL(url);
|
|
@@ -40,7 +45,7 @@ function resolveBaseUrl(baseUrl, input) {
|
|
|
40
45
|
} catch {
|
|
41
46
|
pathname = inputString;
|
|
42
47
|
}
|
|
43
|
-
return host ? inputString :
|
|
48
|
+
return host ? inputString : joinUrlParts(baseUrl, pathname || "");
|
|
44
49
|
}
|
|
45
50
|
/**
|
|
46
51
|
* Replaces the path of a URL with a new path
|
|
@@ -51,7 +56,7 @@ function resolveBaseUrl(baseUrl, input) {
|
|
|
51
56
|
function replaceUrlPath(baseUrl, path) {
|
|
52
57
|
if (parseUrl(path)) return path;
|
|
53
58
|
const { origin } = isString(baseUrl) ? new URL(baseUrl) : baseUrl;
|
|
54
|
-
return
|
|
59
|
+
return joinUrlParts(origin, path);
|
|
55
60
|
}
|
|
56
61
|
/**
|
|
57
62
|
* Resolves a server-provided pagination "next" URL against the current request
|
package/dist/util/url.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url.js","names":[],"sources":["../../lib/util/url.ts"],"sourcesContent":["import {\n isArray,\n isNonEmptyString,\n isObject,\n isString,\n isUrlInstance,\n} from '@sindresorhus/is';\n// oxlint-disable-next-line no-restricted-imports\nimport _parseLinkHeader from 'parse-link-header';\nimport urlJoin from 'url-join';\nimport { logger } from '../logger/index.ts';\nimport { regEx } from './regex.ts';\n\nexport function joinUrlParts(...parts: string[]): string {\n return urlJoin(...parts);\n}\n\nexport function ensurePathPrefix(url: string, prefix: string): string {\n const parsed = new URL(url);\n const fullPath = parsed.pathname + parsed.search;\n if (fullPath.startsWith(prefix)) {\n return url;\n }\n return parsed.origin + prefix + fullPath;\n}\n\nexport function ensureTrailingSlash(url: string): string {\n return url.replace(regEx(/\\/?$/), '/');\n}\n\nexport function trimTrailingSlash(url: string): string {\n return url.replace(regEx(/\\/+$/), '');\n}\n\nexport function trimLeadingSlash(path: string): string {\n return path.replace(regEx(/^\\/+/), '');\n}\n\nexport function trimSlashes(path: string): string {\n return trimLeadingSlash(trimTrailingSlash(path));\n}\n\n/**\n * Resolves an input path against a base URL\n *\n * @param baseUrl - base URL to resolve against\n * @param input - input path (if this is a full URL, it will be returned)\n */\nexport function resolveBaseUrl(baseUrl: string, input: string | URL): string {\n const inputString = input.toString();\n\n let host;\n let pathname;\n try {\n ({ host, pathname } = new URL(inputString));\n } catch {\n pathname = inputString;\n }\n\n return host ? inputString : urlJoin(baseUrl, pathname || '');\n}\n\n/**\n * Replaces the path of a URL with a new path\n *\n * @param baseUrl - source URL\n * @param path - replacement path (if this is a full URL, it will be returned)\n */\nexport function replaceUrlPath(baseUrl: string | URL, path: string): string {\n if (parseUrl(path)) {\n return path;\n }\n\n const { origin } = isString(baseUrl) ? new URL(baseUrl) : baseUrl;\n return urlJoin(origin, path);\n}\n\n/**\n * Resolves a server-provided pagination \"next\" URL against the current request\n * URL, returning it only when it stays on the same origin.\n *\n * Registries paginate by returning a `Link` header (or Atom `<link rel=\"next\">`)\n * pointing at the next page.\n *\n * However, if we were to follow that URL without validating it, this could lead to us being redirected to a different host, which could lead to Server-Side Request Forgery (SSRF).\n *\n * This guard drops any `next` URL that resolves to a different origin.\n *\n * @param baseUrl - the URL of the request that produced `nextUrl`\n * @param nextUrl - the remote-server-provided pagination target (may be relative, and may be malicious)\n * @returns the resolved absolute URL if same-origin, otherwise `null`\n */\nexport function resolveSameOriginUrl(\n baseUrl: string | URL,\n nextUrl: string | URL,\n): string | null {\n const base = parseUrl(baseUrl);\n if (!base) {\n return null;\n }\n\n let resolved: URL;\n try {\n resolved = new URL(nextUrl.toString(), base);\n } catch {\n return null;\n }\n\n // If the base URL is HTTPS and the resolved URL is HTTP, but has no port specified, we can assume that the server intended to use HTTPS. This is a common misconfiguration in some registries.\n if (\n base.protocol === 'https:' &&\n resolved.protocol === 'http:' &&\n resolved.port === ''\n ) {\n logger.debug(`Detected protocol downgrade and fixed it: ${resolved.href}`);\n resolved.protocol = 'https:';\n }\n\n return resolved.origin === base.origin ? resolved.href : null;\n}\n\nexport function getQueryString(params: Record<string, any>): string {\n const usp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (isArray<object>(v)) {\n for (const item of v) {\n // TODO: fix me?\n // oxlint-disable-next-line typescript/no-base-to-string\n usp.append(k, item.toString());\n }\n } else {\n usp.append(k, v.toString());\n }\n }\n return usp.toString();\n}\n\nexport function isHttpUrl(url: unknown): boolean {\n if (!isNonEmptyString(url) && !isUrlInstance(url)) {\n return false;\n }\n const protocol = parseUrl(url)?.protocol;\n return protocol === 'https:' || protocol === 'http:';\n}\n\nexport function parseUrl(url: URL | string | undefined | null): URL | null {\n if (!url) {\n return null;\n }\n\n if (url instanceof URL) {\n return url;\n }\n\n try {\n return new URL(url);\n } catch {\n return null;\n }\n}\n\n/**\n * Tries to create an URL object from either a full URL string or a hostname\n * @param url either the full url or a hostname\n * @returns an URL object or null\n */\nexport function createURLFromHostOrURL(url: string): URL | null {\n return parseUrl(url) ?? parseUrl(`https://${url}`);\n}\n\n/**\n * Removes the `user:password@` userinfo from a URL.\n *\n * Registries are sometimes configured with a URL that carries a placeholder such as `${USER}:${PASS}@`, and credentials embedded this way should not stop the URL from matching a plain `matchHost`.\n *\n * @returns the URL's `href` without userinfo, or `null` if `url` cannot be parsed\n */\nexport function stripUrlCredentials(url: URL): string;\nexport function stripUrlCredentials(url: string | URL): string | null;\nexport function stripUrlCredentials(url: string | URL): string | null {\n const parsed = parseUrl(url);\n if (!parsed) {\n return null;\n }\n if (!parsed.username && !parsed.password) {\n return parsed.href;\n }\n // clone rather than mutate: `parseUrl` returns the same instance it was given when `url` is already a `URL`\n const stripped = new URL(parsed.href);\n stripped.username = '';\n stripped.password = '';\n return stripped.href;\n}\n\n/**\n * Matches the `scheme` and the `userinfo` of every URL inside a text, the userinfo ending at the last `@` before the path as in the URL standard.\n *\n * The text may not be a single URL but may contain several, e.g. a `replaceString`, a `managerData` value or a cache key, so we cannot perform URL parsing as-is\n *\n * A single URL is handled by {@link stripUrlCredentials} instead.\n */\nexport const urlUserinfoRegex = regEx(\n /(?<scheme>[a-z][a-z\\d+.-]*):\\/\\/(?<userinfo>[^/?#\\s]+)@/gi,\n);\n\n/**\n * Matches exactly one `${NAME}`, `${env.NAME}`, `${NAME:?msg}`, `${NAME?msg}`, upper-case `$NAME`, `${{ expression }}` or `{{ NAME }}` reference.\n */\nconst userinfoPlaceholderRegex = regEx(\n /^(?:\\$\\{(?:env\\.)?[A-Za-z_]\\w*(?::?\\?[^{}]*)?\\}|\\$[A-Z_][A-Z\\d_]*|\\$\\{\\{\\s*[^{}\\s][^{}]*\\}\\}|\\{\\{\\s*[A-Za-z_]\\w*\\s*\\}\\})$/,\n);\n\n/**\n * Usernames without a password of their own, which stand for a credential resolved at request time.\n *\n * `oauth2accesstoken` is Google Artifact Registry's, whose token the pypi and docker datasources fetch per request.\n *\n * @see https://docs.renovatebot.com/docker/#google-container-registry-google-artifact-registry\n */\nconst placeholderUsernames = new Set(['oauth2accesstoken']);\n\n/**\n * Whether `username` is a placeholder username such as `oauth2accesstoken`.\n */\nexport function isPlaceholderUsername(username: string): boolean {\n return placeholderUsernames.has(username);\n}\n\n/**\n * Decodes a percent-encoded userinfo part, or returns it as is when it is not valid percent-encoding.\n */\nexport function decodeUserinfo(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n/**\n * Whether a percent-encoded userinfo part, once decoded, is exactly one `${NAME}`, `${env.NAME}`, `${NAME:?msg}`, `${NAME?msg}`, upper-case `$NAME`, `${{ expression }}` or `{{ NAME }}` placeholder.\n */\nexport function isUserinfoPlaceholder(value: string): boolean {\n return userinfoPlaceholderRegex.test(decodeUserinfo(value));\n}\n\nconst firstPathSegmentRegex = regEx(/^[^/?#\\s]*\\/(?<segment>[^/?#\\s]*)/);\n\n/**\n * Whether `username` equals, ignoring case, the first path segment of `rest`, the text following the `@` of a URL's userinfo.\n */\nfunction isFirstPathSegment(username: string, rest: string): boolean {\n const match = firstPathSegmentRegex.exec(rest);\n if (!match) {\n return false;\n }\n const { segment } = match.groups!;\n return decodeUserinfo(segment).toLowerCase() === username.toLowerCase();\n}\n\n/**\n * The minimum length of a username without a password which counts as a credential.\n */\nconst minUsernameCredentialLength = 16;\n\n/**\n * Whether a `scheme://userinfo@` followed by `rest` holds a credential once its parts are decoded:\n * a non-empty password other than a template placeholder, or a username of at least {@link minUsernameCredentialLength} characters with an empty or no password, except for an ssh scheme, a template placeholder, a placeholder username or a username equal to the first path segment.\n * A `docker://image:tag@digest` reference holds none.\n */\nexport function isCredentialUserinfo(\n scheme: string,\n userinfo: string,\n rest: string,\n): boolean {\n const lowerScheme = scheme.toLowerCase();\n if (lowerScheme === 'docker') {\n return false;\n }\n const [rawUsername, ...rawPasswordParts] = userinfo.split(':');\n const rawPassword = rawPasswordParts.join(':');\n if (rawPassword) {\n // a username next to a placeholder password is not a token of its own\n return !isUserinfoPlaceholder(rawPassword);\n }\n const username = decodeUserinfo(rawUsername);\n return (\n username.length >= minUsernameCredentialLength &&\n !lowerScheme.endsWith('ssh') &&\n !isUserinfoPlaceholder(rawUsername) &&\n !isPlaceholderUsername(username) &&\n !isFirstPathSegment(username, rest)\n );\n}\n\n/**\n * Whether `text` contains a URL with a `user:password@` or `token@` userinfo.\n */\nexport function hasUrlCredentials(text: string): boolean {\n for (const match of text.matchAll(urlUserinfoRegex)) {\n const { scheme, userinfo } = match.groups!;\n const rest = text.slice(match.index + match[0].length);\n if (isCredentialUserinfo(scheme, userinfo, rest)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Whether any string inside `value`, searched through arrays and object values, contains a URL with credentials.\n */\nexport function containsUrlCredentials(value: unknown): boolean {\n if (isString(value)) {\n return hasUrlCredentials(value);\n }\n if (isObject(value)) {\n return Object.values(value).some(containsUrlCredentials);\n }\n return false;\n}\n\n/**\n * Removes the `user:password@` or `token@` userinfo of every URL inside `text` that carries a credential, leaving the rest of `text` unchanged.\n */\nexport function withoutUrlCredentials(text: string): string {\n return text.replace(urlUserinfoRegex, (match: string, ...args: unknown[]) => {\n // the offset, the input and the named groups are the last arguments of a `replace()` callback\n const offset = args.at(-3) as number;\n const { scheme, userinfo } = args.at(-1) as Record<string, string>;\n const rest = text.slice(offset + match.length);\n return isCredentialUserinfo(scheme, userinfo, rest)\n ? `${scheme}://`\n : match;\n });\n}\n\nexport type LinkHeaderLinks = _parseLinkHeader.Links;\n\nexport function parseLinkHeader(\n linkHeader: string | null | undefined,\n): LinkHeaderLinks | null {\n if (!isNonEmptyString(linkHeader)) {\n return null;\n }\n if (linkHeader.length > 2000) {\n logger.warn({ linkHeader }, 'Link header too long.');\n return null;\n }\n return _parseLinkHeader(linkHeader);\n}\n\n/**\n * prefix https:// to hosts with port or path\n */\nexport function massageHostUrl(url: string): string {\n if (!url.includes('://') && url.includes('/')) {\n return `https://${url}`;\n }\n if (!url.includes('://') && url.includes(':')) {\n return `https://${url}`;\n }\n return url;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"url.js","names":[],"sources":["../../lib/util/url.ts"],"sourcesContent":["import {\n isArray,\n isNonEmptyString,\n isObject,\n isString,\n isUrlInstance,\n} from '@sindresorhus/is';\n// oxlint-disable-next-line no-restricted-imports\nimport _parseLinkHeader from 'parse-link-header';\nimport urlJoin from 'url-join';\nimport { logger } from '../logger/index.ts';\nimport { regEx } from './regex.ts';\n\n/**\n * Joins URL parts with `url-join`. The result ends with a slash only if the last\n * part does.\n */\nexport function joinUrlParts(...parts: string[]): string {\n const joined = urlJoin(...parts);\n return parts.at(-1)?.endsWith('/')\n ? ensureTrailingSlash(joined)\n : trimTrailingSlash(joined);\n}\n\nexport function ensurePathPrefix(url: string, prefix: string): string {\n const parsed = new URL(url);\n const fullPath = parsed.pathname + parsed.search;\n if (fullPath.startsWith(prefix)) {\n return url;\n }\n return parsed.origin + prefix + fullPath;\n}\n\nexport function ensureTrailingSlash(url: string): string {\n return url.replace(regEx(/\\/?$/), '/');\n}\n\nexport function trimTrailingSlash(url: string): string {\n return url.replace(regEx(/\\/+$/), '');\n}\n\nexport function trimLeadingSlash(path: string): string {\n return path.replace(regEx(/^\\/+/), '');\n}\n\nexport function trimSlashes(path: string): string {\n return trimLeadingSlash(trimTrailingSlash(path));\n}\n\n/**\n * Resolves an input path against a base URL\n *\n * @param baseUrl - base URL to resolve against\n * @param input - input path (if this is a full URL, it will be returned)\n */\nexport function resolveBaseUrl(baseUrl: string, input: string | URL): string {\n const inputString = input.toString();\n\n let host;\n let pathname;\n try {\n ({ host, pathname } = new URL(inputString));\n } catch {\n pathname = inputString;\n }\n\n return host ? inputString : joinUrlParts(baseUrl, pathname || '');\n}\n\n/**\n * Replaces the path of a URL with a new path\n *\n * @param baseUrl - source URL\n * @param path - replacement path (if this is a full URL, it will be returned)\n */\nexport function replaceUrlPath(baseUrl: string | URL, path: string): string {\n if (parseUrl(path)) {\n return path;\n }\n\n const { origin } = isString(baseUrl) ? new URL(baseUrl) : baseUrl;\n return joinUrlParts(origin, path);\n}\n\n/**\n * Resolves a server-provided pagination \"next\" URL against the current request\n * URL, returning it only when it stays on the same origin.\n *\n * Registries paginate by returning a `Link` header (or Atom `<link rel=\"next\">`)\n * pointing at the next page.\n *\n * However, if we were to follow that URL without validating it, this could lead to us being redirected to a different host, which could lead to Server-Side Request Forgery (SSRF).\n *\n * This guard drops any `next` URL that resolves to a different origin.\n *\n * @param baseUrl - the URL of the request that produced `nextUrl`\n * @param nextUrl - the remote-server-provided pagination target (may be relative, and may be malicious)\n * @returns the resolved absolute URL if same-origin, otherwise `null`\n */\nexport function resolveSameOriginUrl(\n baseUrl: string | URL,\n nextUrl: string | URL,\n): string | null {\n const base = parseUrl(baseUrl);\n if (!base) {\n return null;\n }\n\n let resolved: URL;\n try {\n resolved = new URL(nextUrl.toString(), base);\n } catch {\n return null;\n }\n\n // If the base URL is HTTPS and the resolved URL is HTTP, but has no port specified, we can assume that the server intended to use HTTPS. This is a common misconfiguration in some registries.\n if (\n base.protocol === 'https:' &&\n resolved.protocol === 'http:' &&\n resolved.port === ''\n ) {\n logger.debug(`Detected protocol downgrade and fixed it: ${resolved.href}`);\n resolved.protocol = 'https:';\n }\n\n return resolved.origin === base.origin ? resolved.href : null;\n}\n\nexport function getQueryString(params: Record<string, any>): string {\n const usp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (isArray<object>(v)) {\n for (const item of v) {\n // TODO: fix me?\n // oxlint-disable-next-line typescript/no-base-to-string\n usp.append(k, item.toString());\n }\n } else {\n usp.append(k, v.toString());\n }\n }\n return usp.toString();\n}\n\nexport function isHttpUrl(url: unknown): boolean {\n if (!isNonEmptyString(url) && !isUrlInstance(url)) {\n return false;\n }\n const protocol = parseUrl(url)?.protocol;\n return protocol === 'https:' || protocol === 'http:';\n}\n\nexport function parseUrl(url: URL | string | undefined | null): URL | null {\n if (!url) {\n return null;\n }\n\n if (url instanceof URL) {\n return url;\n }\n\n try {\n return new URL(url);\n } catch {\n return null;\n }\n}\n\n/**\n * Tries to create an URL object from either a full URL string or a hostname\n * @param url either the full url or a hostname\n * @returns an URL object or null\n */\nexport function createURLFromHostOrURL(url: string): URL | null {\n return parseUrl(url) ?? parseUrl(`https://${url}`);\n}\n\n/**\n * Removes the `user:password@` userinfo from a URL.\n *\n * Registries are sometimes configured with a URL that carries a placeholder such as `${USER}:${PASS}@`, and credentials embedded this way should not stop the URL from matching a plain `matchHost`.\n *\n * @returns the URL's `href` without userinfo, or `null` if `url` cannot be parsed\n */\nexport function stripUrlCredentials(url: URL): string;\nexport function stripUrlCredentials(url: string | URL): string | null;\nexport function stripUrlCredentials(url: string | URL): string | null {\n const parsed = parseUrl(url);\n if (!parsed) {\n return null;\n }\n if (!parsed.username && !parsed.password) {\n return parsed.href;\n }\n // clone rather than mutate: `parseUrl` returns the same instance it was given when `url` is already a `URL`\n const stripped = new URL(parsed.href);\n stripped.username = '';\n stripped.password = '';\n return stripped.href;\n}\n\n/**\n * Matches the `scheme` and the `userinfo` of every URL inside a text, the userinfo ending at the last `@` before the path as in the URL standard.\n *\n * The text may not be a single URL but may contain several, e.g. a `replaceString`, a `managerData` value or a cache key, so we cannot perform URL parsing as-is\n *\n * A single URL is handled by {@link stripUrlCredentials} instead.\n */\nexport const urlUserinfoRegex = regEx(\n /(?<scheme>[a-z][a-z\\d+.-]*):\\/\\/(?<userinfo>[^/?#\\s]+)@/gi,\n);\n\n/**\n * Matches exactly one `${NAME}`, `${env.NAME}`, `${NAME:?msg}`, `${NAME?msg}`, upper-case `$NAME`, `${{ expression }}` or `{{ NAME }}` reference.\n */\nconst userinfoPlaceholderRegex = regEx(\n /^(?:\\$\\{(?:env\\.)?[A-Za-z_]\\w*(?::?\\?[^{}]*)?\\}|\\$[A-Z_][A-Z\\d_]*|\\$\\{\\{\\s*[^{}\\s][^{}]*\\}\\}|\\{\\{\\s*[A-Za-z_]\\w*\\s*\\}\\})$/,\n);\n\n/**\n * Usernames without a password of their own, which stand for a credential resolved at request time.\n *\n * `oauth2accesstoken` is Google Artifact Registry's, whose token the pypi and docker datasources fetch per request.\n *\n * @see https://docs.renovatebot.com/docker/#google-container-registry-google-artifact-registry\n */\nconst placeholderUsernames = new Set(['oauth2accesstoken']);\n\n/**\n * Whether `username` is a placeholder username such as `oauth2accesstoken`.\n */\nexport function isPlaceholderUsername(username: string): boolean {\n return placeholderUsernames.has(username);\n}\n\n/**\n * Decodes a percent-encoded userinfo part, or returns it as is when it is not valid percent-encoding.\n */\nexport function decodeUserinfo(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n/**\n * Whether a percent-encoded userinfo part, once decoded, is exactly one `${NAME}`, `${env.NAME}`, `${NAME:?msg}`, `${NAME?msg}`, upper-case `$NAME`, `${{ expression }}` or `{{ NAME }}` placeholder.\n */\nexport function isUserinfoPlaceholder(value: string): boolean {\n return userinfoPlaceholderRegex.test(decodeUserinfo(value));\n}\n\nconst firstPathSegmentRegex = regEx(/^[^/?#\\s]*\\/(?<segment>[^/?#\\s]*)/);\n\n/**\n * Whether `username` equals, ignoring case, the first path segment of `rest`, the text following the `@` of a URL's userinfo.\n */\nfunction isFirstPathSegment(username: string, rest: string): boolean {\n const match = firstPathSegmentRegex.exec(rest);\n if (!match) {\n return false;\n }\n const { segment } = match.groups!;\n return decodeUserinfo(segment).toLowerCase() === username.toLowerCase();\n}\n\n/**\n * The minimum length of a username without a password which counts as a credential.\n */\nconst minUsernameCredentialLength = 16;\n\n/**\n * Whether a `scheme://userinfo@` followed by `rest` holds a credential once its parts are decoded:\n * a non-empty password other than a template placeholder, or a username of at least {@link minUsernameCredentialLength} characters with an empty or no password, except for an ssh scheme, a template placeholder, a placeholder username or a username equal to the first path segment.\n * A `docker://image:tag@digest` reference holds none.\n */\nexport function isCredentialUserinfo(\n scheme: string,\n userinfo: string,\n rest: string,\n): boolean {\n const lowerScheme = scheme.toLowerCase();\n if (lowerScheme === 'docker') {\n return false;\n }\n const [rawUsername, ...rawPasswordParts] = userinfo.split(':');\n const rawPassword = rawPasswordParts.join(':');\n if (rawPassword) {\n // a username next to a placeholder password is not a token of its own\n return !isUserinfoPlaceholder(rawPassword);\n }\n const username = decodeUserinfo(rawUsername);\n return (\n username.length >= minUsernameCredentialLength &&\n !lowerScheme.endsWith('ssh') &&\n !isUserinfoPlaceholder(rawUsername) &&\n !isPlaceholderUsername(username) &&\n !isFirstPathSegment(username, rest)\n );\n}\n\n/**\n * Whether `text` contains a URL with a `user:password@` or `token@` userinfo.\n */\nexport function hasUrlCredentials(text: string): boolean {\n for (const match of text.matchAll(urlUserinfoRegex)) {\n const { scheme, userinfo } = match.groups!;\n const rest = text.slice(match.index + match[0].length);\n if (isCredentialUserinfo(scheme, userinfo, rest)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Whether any string inside `value`, searched through arrays and object values, contains a URL with credentials.\n */\nexport function containsUrlCredentials(value: unknown): boolean {\n if (isString(value)) {\n return hasUrlCredentials(value);\n }\n if (isObject(value)) {\n return Object.values(value).some(containsUrlCredentials);\n }\n return false;\n}\n\n/**\n * Removes the `user:password@` or `token@` userinfo of every URL inside `text` that carries a credential, leaving the rest of `text` unchanged.\n */\nexport function withoutUrlCredentials(text: string): string {\n return text.replace(urlUserinfoRegex, (match: string, ...args: unknown[]) => {\n // the offset, the input and the named groups are the last arguments of a `replace()` callback\n const offset = args.at(-3) as number;\n const { scheme, userinfo } = args.at(-1) as Record<string, string>;\n const rest = text.slice(offset + match.length);\n return isCredentialUserinfo(scheme, userinfo, rest)\n ? `${scheme}://`\n : match;\n });\n}\n\nexport type LinkHeaderLinks = _parseLinkHeader.Links;\n\nexport function parseLinkHeader(\n linkHeader: string | null | undefined,\n): LinkHeaderLinks | null {\n if (!isNonEmptyString(linkHeader)) {\n return null;\n }\n if (linkHeader.length > 2000) {\n logger.warn({ linkHeader }, 'Link header too long.');\n return null;\n }\n return _parseLinkHeader(linkHeader);\n}\n\n/**\n * prefix https:// to hosts with port or path\n */\nexport function massageHostUrl(url: string): string {\n if (!url.includes('://') && url.includes('/')) {\n return `https://${url}`;\n }\n if (!url.includes('://') && url.includes(':')) {\n return `https://${url}`;\n }\n return url;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "renovate",
|
|
3
3
|
"description": "Automated dependency updates. Flexible so you don't need to be.",
|
|
4
|
-
"version": "44.222.
|
|
4
|
+
"version": "44.222.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"renovate": "dist/renovate.js",
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
"tar": "7.5.22",
|
|
204
204
|
"toml-eslint-parser": "1.0.3",
|
|
205
205
|
"upath": "3.0.8",
|
|
206
|
-
"url-join": "5.
|
|
206
|
+
"url-join": "5.2.0",
|
|
207
207
|
"validate-npm-package-name": "7.0.2",
|
|
208
208
|
"xmldoc": "3.0.0",
|
|
209
209
|
"yaml": "2.9.1",
|
package/renovate-schema.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$id": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
-
"title": "JSON schema for Renovate 44.222.
|
|
3
|
+
"title": "JSON schema for Renovate 44.222.6 config files (https://renovatebot.com/)",
|
|
4
4
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
5
|
-
"x-renovate-version": "44.222.
|
|
5
|
+
"x-renovate-version": "44.222.6",
|
|
6
6
|
"allowComments": true,
|
|
7
7
|
"type": "object",
|
|
8
8
|
"definitions": {
|