89 lines
3.1 KiB
JavaScript
89 lines
3.1 KiB
JavaScript
'use strict';
|
|
|
|
import AxiosError, { REDACTED } from './AxiosError.js';
|
|
import isAbsoluteURL from '../helpers/isAbsoluteURL.js';
|
|
import combineURLs from '../helpers/combineURLs.js';
|
|
|
|
const malformedHttpProtocol = /^https?:(?!\/\/)/i;
|
|
const httpProtocolControlCharacters = /[\t\n\r]/g;
|
|
|
|
function stripLeadingC0ControlOrSpace(url) {
|
|
let i = 0;
|
|
while (i < url.length && url.charCodeAt(i) <= 0x20) {
|
|
i++;
|
|
}
|
|
return url.slice(i);
|
|
}
|
|
|
|
function normalizeURLForProtocolCheck(url) {
|
|
return stripLeadingC0ControlOrSpace(url).replace(httpProtocolControlCharacters, '');
|
|
}
|
|
|
|
// Redact the parts of a URL that can carry secrets before it is embedded in an
|
|
// error message. AxiosError.toJSON() serializes `message` verbatim and errors
|
|
// are commonly logged, while the opt-in `config.redact` model only cleans
|
|
// config keys — it cannot reach the message. Redact only the genuinely
|
|
// sensitive substrings — userinfo (credentials), query parameter values and
|
|
// fragment contents — with the same REDACTED marker the config redaction uses,
|
|
// while keeping the scheme, host, path and parameter names so the offending
|
|
// request stays accurately identifiable.
|
|
function redactFragment(fragment) {
|
|
if (!fragment) {
|
|
return fragment;
|
|
}
|
|
|
|
return fragment.replace(/(^|&)([^=&]*=)?[^&]+/g, (match, separator, parameterName = '') => {
|
|
return `${separator}${parameterName}${REDACTED}`;
|
|
});
|
|
}
|
|
|
|
function redactSensitiveURLParts(url) {
|
|
const redactedURL = url.replace(/^(https?:\/{0,2})[^/?#]*@/i, `$1${REDACTED}@`);
|
|
const fragmentIndex = redactedURL.indexOf('#');
|
|
const urlWithoutFragment =
|
|
fragmentIndex === -1 ? redactedURL : redactedURL.slice(0, fragmentIndex);
|
|
const redactedURLWithoutFragment = urlWithoutFragment.replace(
|
|
/([?&][^=&#]*=)[^&#]*/g,
|
|
`$1${REDACTED}`
|
|
);
|
|
|
|
if (fragmentIndex === -1) {
|
|
return redactedURLWithoutFragment;
|
|
}
|
|
|
|
return `${redactedURLWithoutFragment}#${redactFragment(redactedURL.slice(fragmentIndex + 1))}`;
|
|
}
|
|
|
|
function assertValidHttpProtocolURL(url, config) {
|
|
if (typeof url === 'string') {
|
|
const normalizedURL = normalizeURLForProtocolCheck(url);
|
|
if (malformedHttpProtocol.test(normalizedURL)) {
|
|
throw new AxiosError(
|
|
`Invalid URL ${JSON.stringify(redactSensitiveURLParts(normalizedURL))}: missing "//" after protocol`,
|
|
AxiosError.ERR_INVALID_URL,
|
|
config
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new URL by combining the baseURL with the requestedURL,
|
|
* only when the requestedURL is not already an absolute URL.
|
|
* If the requestURL is absolute, this function returns the requestedURL untouched.
|
|
*
|
|
* @param {string} baseURL The base URL
|
|
* @param {string} requestedURL Absolute or relative URL to combine
|
|
*
|
|
* @returns {string} The combined full path
|
|
*/
|
|
export default function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls, config) {
|
|
assertValidHttpProtocolURL(requestedURL, config);
|
|
let isRelativeUrl = !isAbsoluteURL(requestedURL);
|
|
if (baseURL && (isRelativeUrl || allowAbsoluteUrls === false)) {
|
|
assertValidHttpProtocolURL(baseURL, config);
|
|
return combineURLs(baseURL, requestedURL);
|
|
}
|
|
return requestedURL;
|
|
}
|