buildFullPath.js 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. 'use strict';
  2. import AxiosError, { REDACTED } from './AxiosError.js';
  3. import isAbsoluteURL from '../helpers/isAbsoluteURL.js';
  4. import combineURLs from '../helpers/combineURLs.js';
  5. const malformedHttpProtocol = /^https?:(?!\/\/)/i;
  6. const httpProtocolControlCharacters = /[\t\n\r]/g;
  7. function stripLeadingC0ControlOrSpace(url) {
  8. let i = 0;
  9. while (i < url.length && url.charCodeAt(i) <= 0x20) {
  10. i++;
  11. }
  12. return url.slice(i);
  13. }
  14. function normalizeURLForProtocolCheck(url) {
  15. return stripLeadingC0ControlOrSpace(url).replace(httpProtocolControlCharacters, '');
  16. }
  17. // Redact the parts of a URL that can carry secrets before it is embedded in an
  18. // error message. AxiosError.toJSON() serializes `message` verbatim and errors
  19. // are commonly logged, while the opt-in `config.redact` model only cleans
  20. // config keys — it cannot reach the message. Redact only the genuinely
  21. // sensitive substrings — userinfo (credentials), query parameter values and
  22. // fragment contents — with the same REDACTED marker the config redaction uses,
  23. // while keeping the scheme, host, path and parameter names so the offending
  24. // request stays accurately identifiable.
  25. function redactFragment(fragment) {
  26. if (!fragment) {
  27. return fragment;
  28. }
  29. return fragment.replace(/(^|&)([^=&]*=)?[^&]+/g, (match, separator, parameterName = '') => {
  30. return `${separator}${parameterName}${REDACTED}`;
  31. });
  32. }
  33. function redactSensitiveURLParts(url) {
  34. const redactedURL = url.replace(/^(https?:\/{0,2})[^/?#]*@/i, `$1${REDACTED}@`);
  35. const fragmentIndex = redactedURL.indexOf('#');
  36. const urlWithoutFragment =
  37. fragmentIndex === -1 ? redactedURL : redactedURL.slice(0, fragmentIndex);
  38. const redactedURLWithoutFragment = urlWithoutFragment.replace(
  39. /([?&][^=&#]*=)[^&#]*/g,
  40. `$1${REDACTED}`
  41. );
  42. if (fragmentIndex === -1) {
  43. return redactedURLWithoutFragment;
  44. }
  45. return `${redactedURLWithoutFragment}#${redactFragment(redactedURL.slice(fragmentIndex + 1))}`;
  46. }
  47. function assertValidHttpProtocolURL(url, config) {
  48. if (typeof url === 'string') {
  49. const normalizedURL = normalizeURLForProtocolCheck(url);
  50. if (malformedHttpProtocol.test(normalizedURL)) {
  51. throw new AxiosError(
  52. `Invalid URL ${JSON.stringify(redactSensitiveURLParts(normalizedURL))}: missing "//" after protocol`,
  53. AxiosError.ERR_INVALID_URL,
  54. config
  55. );
  56. }
  57. }
  58. }
  59. /**
  60. * Creates a new URL by combining the baseURL with the requestedURL,
  61. * only when the requestedURL is not already an absolute URL.
  62. * If the requestURL is absolute, this function returns the requestedURL untouched.
  63. *
  64. * @param {string} baseURL The base URL
  65. * @param {string} requestedURL Absolute or relative URL to combine
  66. *
  67. * @returns {string} The combined full path
  68. */
  69. export default function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls, config) {
  70. assertValidHttpProtocolURL(requestedURL, config);
  71. let isRelativeUrl = !isAbsoluteURL(requestedURL);
  72. if (baseURL && (isRelativeUrl || allowAbsoluteUrls === false)) {
  73. assertValidHttpProtocolURL(baseURL, config);
  74. return combineURLs(baseURL, requestedURL);
  75. }
  76. return requestedURL;
  77. }