shouldBypassProxy.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. const LOOPBACK_HOSTNAMES = new Set(['localhost']);
  2. const isIPv4Loopback = (host) => {
  3. const parts = host.split('.');
  4. if (parts.length !== 4) return false;
  5. if (parts[0] !== '127') return false;
  6. return parts.every((p) => /^\d+$/.test(p) && Number(p) >= 0 && Number(p) <= 255);
  7. };
  8. const isIPv6Loopback = (host) => {
  9. // Collapse all-zero groups: any form of ::1 / 0:0:...:0:1
  10. // First, strip any leading "::" by normalising with Set lookup of common forms,
  11. // then fall back to structural check.
  12. if (host === '::1') return true;
  13. // Check IPv4-mapped IPv6 loopback: ::ffff:<v4-loopback> or ::ffff:<hex-v4-loopback>
  14. // Node's URL parser normalises ::ffff:127.0.0.1 → ::ffff:7f00:1
  15. const v4MappedDotted = host.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
  16. if (v4MappedDotted) return isIPv4Loopback(v4MappedDotted[1]);
  17. const v4MappedHex = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
  18. if (v4MappedHex) {
  19. const high = parseInt(v4MappedHex[1], 16);
  20. // High 16 bits must start with 127 (0x7f) — i.e. 0x7f00..0x7fff
  21. return high >= 0x7f00 && high <= 0x7fff;
  22. }
  23. // Full-form ::1 variants: any number of zero groups followed by trailing 1
  24. // e.g. 0:0:0:0:0:0:0:1, 0000:...:0001
  25. const groups = host.split(':');
  26. if (groups.length === 8) {
  27. for (let i = 0; i < 7; i++) {
  28. if (!/^0+$/.test(groups[i])) return false;
  29. }
  30. return /^0*1$/.test(groups[7]);
  31. }
  32. return false;
  33. };
  34. const isLoopback = (host) => {
  35. if (!host) return false;
  36. if (LOOPBACK_HOSTNAMES.has(host)) return true;
  37. if (isIPv4Loopback(host)) return true;
  38. return isIPv6Loopback(host);
  39. };
  40. const DEFAULT_PORTS = {
  41. http: 80,
  42. https: 443,
  43. ws: 80,
  44. wss: 443,
  45. ftp: 21,
  46. };
  47. const parseNoProxyEntry = (entry) => {
  48. let entryHost = entry;
  49. let entryPort = 0;
  50. if (entryHost.charAt(0) === '[') {
  51. const bracketIndex = entryHost.indexOf(']');
  52. if (bracketIndex !== -1) {
  53. const host = entryHost.slice(1, bracketIndex);
  54. const rest = entryHost.slice(bracketIndex + 1);
  55. if (rest.charAt(0) === ':' && /^\d+$/.test(rest.slice(1))) {
  56. entryPort = Number.parseInt(rest.slice(1), 10);
  57. }
  58. return [host, entryPort];
  59. }
  60. }
  61. const firstColon = entryHost.indexOf(':');
  62. const lastColon = entryHost.lastIndexOf(':');
  63. if (
  64. firstColon !== -1 &&
  65. firstColon === lastColon &&
  66. /^\d+$/.test(entryHost.slice(lastColon + 1))
  67. ) {
  68. entryPort = Number.parseInt(entryHost.slice(lastColon + 1), 10);
  69. entryHost = entryHost.slice(0, lastColon);
  70. }
  71. return [entryHost, entryPort];
  72. };
  73. const normalizeNoProxyHost = (hostname) => {
  74. if (!hostname) {
  75. return hostname;
  76. }
  77. if (hostname.charAt(0) === '[' && hostname.charAt(hostname.length - 1) === ']') {
  78. hostname = hostname.slice(1, -1);
  79. }
  80. return hostname.replace(/\.+$/, '');
  81. };
  82. export default function shouldBypassProxy(location) {
  83. let parsed;
  84. try {
  85. parsed = new URL(location);
  86. } catch (_err) {
  87. return false;
  88. }
  89. const noProxy = (process.env.no_proxy || process.env.NO_PROXY || '').toLowerCase();
  90. if (!noProxy) {
  91. return false;
  92. }
  93. if (noProxy === '*') {
  94. return true;
  95. }
  96. const port =
  97. Number.parseInt(parsed.port, 10) || DEFAULT_PORTS[parsed.protocol.split(':', 1)[0]] || 0;
  98. const hostname = normalizeNoProxyHost(parsed.hostname.toLowerCase());
  99. return noProxy.split(/[\s,]+/).some((entry) => {
  100. if (!entry) {
  101. return false;
  102. }
  103. let [entryHost, entryPort] = parseNoProxyEntry(entry);
  104. entryHost = normalizeNoProxyHost(entryHost);
  105. if (!entryHost) {
  106. return false;
  107. }
  108. if (entryPort && entryPort !== port) {
  109. return false;
  110. }
  111. if (entryHost.charAt(0) === '*') {
  112. entryHost = entryHost.slice(1);
  113. }
  114. if (entryHost.charAt(0) === '.') {
  115. return hostname.endsWith(entryHost);
  116. }
  117. return hostname === entryHost || (isLoopback(hostname) && isLoopback(entryHost));
  118. });
  119. }