isomorphic-git 1.41.0 → 1.41.1

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.
@@ -2485,21 +2485,76 @@ async function collect(iterable) {
2485
2485
  return result
2486
2486
  }
2487
2487
 
2488
+ // The `@` that ends the credentials is the last one in the authority, and the
2489
+ // scheme is matched case-insensitively: `HTTPS://user:pass@host/x` is a URL
2490
+ // like any other, and leaving the credentials in it is what `fetch` rejects.
2491
+ // The authority ends at the first `/`, `?` or `#`, so an `@` that lives in a
2492
+ // query or a fragment is left where it is.
2493
+ const CREDENTIALS = /^(https?:\/\/)([^/?#]+)@/i;
2494
+
2488
2495
  function extractAuthFromUrl(url) {
2489
2496
  // For whatever reason, the `fetch` API does not convert credentials embedded in the URL
2490
2497
  // into Basic Authentication headers automatically. Instead it throws an error!
2491
2498
  // So we must manually parse the URL, rip out the user:password portion if it is present
2492
2499
  // and compute the Authorization header.
2493
- // Note: I tried using new URL(url) but that throws a security exception in Edge. :rolleyes:
2494
- let userpass = url.match(/^https?:\/\/([^/]+)@/);
2500
+ const credentials = url.match(CREDENTIALS);
2495
2501
  // No credentials, return the url unmodified and an empty auth object
2496
- if (userpass == null) return { url, auth: {} }
2497
- userpass = userpass[1];
2498
- const [username, password] = userpass.split(':');
2499
- // Remove credentials from URL
2500
- url = url.replace(`${userpass}@`, '');
2501
- // Has credentials, return the fetch-safe URL and the parsed credentials
2502
- return { url, auth: { username, password } }
2502
+ if (credentials == null) return { url, auth: {} }
2503
+ // Has credentials, return the fetch-safe URL and the parsed credentials.
2504
+ // The credentials are cut out of the original string rather than read back
2505
+ // off the parsed URL: serializing that one would also drop an explicit
2506
+ // `:443` and append a slash to a URL that has no path, and this return value
2507
+ // is what the request goes out against.
2508
+ return {
2509
+ url: url.replace(CREDENTIALS, '$1'),
2510
+ auth: parseUserinfo(url, credentials[2]),
2511
+ }
2512
+ }
2513
+
2514
+ function parseUserinfo(url, userpass) {
2515
+ // `URL` splits the two halves the way the platform does, so the first colon
2516
+ // separates them and any later one stays in the password. Only the split is
2517
+ // taken from it, for the reason above.
2518
+ const parsed = parseUrl(url);
2519
+ // A password is absent, not empty, when no colon was written at all. `URL`
2520
+ // reports `''` for both, and the difference is worth keeping.
2521
+ const hasPassword = userpass.indexOf(':') !== -1;
2522
+
2523
+ if (parsed !== null) {
2524
+ return {
2525
+ username: decodeUserinfo(parsed.username),
2526
+ password: hasPassword ? decodeUserinfo(parsed.password) : undefined,
2527
+ }
2528
+ }
2529
+
2530
+ // No `URL` to parse with, or it rejected a string the expression above
2531
+ // accepted. Split on the first colon by hand, without the percent-decoding.
2532
+ const separatorIndex = userpass.indexOf(':');
2533
+ return {
2534
+ username: hasPassword ? userpass.slice(0, separatorIndex) : userpass,
2535
+ password: hasPassword ? userpass.slice(separatorIndex + 1) : undefined,
2536
+ }
2537
+ }
2538
+
2539
+ function parseUrl(url) {
2540
+ if (typeof URL !== 'function') return null
2541
+ try {
2542
+ return new URL(url)
2543
+ } catch (_) {
2544
+ return null
2545
+ }
2546
+ }
2547
+
2548
+ // `URL` hands back the userinfo still percent-encoded, so `p:ss` arrives as
2549
+ // `p%3Ass` and has to be decoded before it reaches the Authorization header. A
2550
+ // lone `%` is not something the parser encodes, and it makes the decode throw,
2551
+ // so the raw value stands in whenever the decode fails.
2552
+ function decodeUserinfo(value) {
2553
+ try {
2554
+ return decodeURIComponent(value)
2555
+ } catch (_) {
2556
+ return value
2557
+ }
2503
2558
  }
2504
2559
 
2505
2560
  class EmptyServerResponseError extends BaseError {
package/managers/index.js CHANGED
@@ -2478,21 +2478,76 @@ async function collect(iterable) {
2478
2478
  return result
2479
2479
  }
2480
2480
 
2481
+ // The `@` that ends the credentials is the last one in the authority, and the
2482
+ // scheme is matched case-insensitively: `HTTPS://user:pass@host/x` is a URL
2483
+ // like any other, and leaving the credentials in it is what `fetch` rejects.
2484
+ // The authority ends at the first `/`, `?` or `#`, so an `@` that lives in a
2485
+ // query or a fragment is left where it is.
2486
+ const CREDENTIALS = /^(https?:\/\/)([^/?#]+)@/i;
2487
+
2481
2488
  function extractAuthFromUrl(url) {
2482
2489
  // For whatever reason, the `fetch` API does not convert credentials embedded in the URL
2483
2490
  // into Basic Authentication headers automatically. Instead it throws an error!
2484
2491
  // So we must manually parse the URL, rip out the user:password portion if it is present
2485
2492
  // and compute the Authorization header.
2486
- // Note: I tried using new URL(url) but that throws a security exception in Edge. :rolleyes:
2487
- let userpass = url.match(/^https?:\/\/([^/]+)@/);
2493
+ const credentials = url.match(CREDENTIALS);
2488
2494
  // No credentials, return the url unmodified and an empty auth object
2489
- if (userpass == null) return { url, auth: {} }
2490
- userpass = userpass[1];
2491
- const [username, password] = userpass.split(':');
2492
- // Remove credentials from URL
2493
- url = url.replace(`${userpass}@`, '');
2494
- // Has credentials, return the fetch-safe URL and the parsed credentials
2495
- return { url, auth: { username, password } }
2495
+ if (credentials == null) return { url, auth: {} }
2496
+ // Has credentials, return the fetch-safe URL and the parsed credentials.
2497
+ // The credentials are cut out of the original string rather than read back
2498
+ // off the parsed URL: serializing that one would also drop an explicit
2499
+ // `:443` and append a slash to a URL that has no path, and this return value
2500
+ // is what the request goes out against.
2501
+ return {
2502
+ url: url.replace(CREDENTIALS, '$1'),
2503
+ auth: parseUserinfo(url, credentials[2]),
2504
+ }
2505
+ }
2506
+
2507
+ function parseUserinfo(url, userpass) {
2508
+ // `URL` splits the two halves the way the platform does, so the first colon
2509
+ // separates them and any later one stays in the password. Only the split is
2510
+ // taken from it, for the reason above.
2511
+ const parsed = parseUrl(url);
2512
+ // A password is absent, not empty, when no colon was written at all. `URL`
2513
+ // reports `''` for both, and the difference is worth keeping.
2514
+ const hasPassword = userpass.indexOf(':') !== -1;
2515
+
2516
+ if (parsed !== null) {
2517
+ return {
2518
+ username: decodeUserinfo(parsed.username),
2519
+ password: hasPassword ? decodeUserinfo(parsed.password) : undefined,
2520
+ }
2521
+ }
2522
+
2523
+ // No `URL` to parse with, or it rejected a string the expression above
2524
+ // accepted. Split on the first colon by hand, without the percent-decoding.
2525
+ const separatorIndex = userpass.indexOf(':');
2526
+ return {
2527
+ username: hasPassword ? userpass.slice(0, separatorIndex) : userpass,
2528
+ password: hasPassword ? userpass.slice(separatorIndex + 1) : undefined,
2529
+ }
2530
+ }
2531
+
2532
+ function parseUrl(url) {
2533
+ if (typeof URL !== 'function') return null
2534
+ try {
2535
+ return new URL(url)
2536
+ } catch (_) {
2537
+ return null
2538
+ }
2539
+ }
2540
+
2541
+ // `URL` hands back the userinfo still percent-encoded, so `p:ss` arrives as
2542
+ // `p%3Ass` and has to be decoded before it reaches the Authorization header. A
2543
+ // lone `%` is not something the parser encodes, and it makes the decode throw,
2544
+ // so the raw value stands in whenever the decode fails.
2545
+ function decodeUserinfo(value) {
2546
+ try {
2547
+ return decodeURIComponent(value)
2548
+ } catch (_) {
2549
+ return value
2550
+ }
2496
2551
  }
2497
2552
 
2498
2553
  class EmptyServerResponseError extends BaseError {