const { URL } = require('node:url');
const net = require('node:net');

const isLoopbackV4 = (address) => {
  // 127.0.0.0/8: first octet = 127
  const octets = address.split('.');
  return (
    octets.length === 4
  ) && parseInt(octets[0], 10) === 127;
}

const isLoopbackV6 = (address) => {
  // new URL(...) follows the WHATWG URL Standard
  // which compresses IPv6 addresses, therefore the IPv6
  // loopback address will always be compressed to '[::1]':
  // https://url.spec.whatwg.org/#concept-ipv6-serializer
  return (address === '::1');
}

const isIpLoopback = (address) => {
  if (net.isIPv4(address)) {
    return isLoopbackV4(address);
  }

  if (net.isIPv6(address)) {
    return isLoopbackV6(address);
  }

  return false;
}

const isNormalizedLocalhostTLD = (host) => {
  return host.toLowerCase().endsWith('.localhost');
}

const isLocalHostname = (host) => {
  return host.toLowerCase() === 'localhost' ||
    isNormalizedLocalhostTLD(host);
}

/**
 * Removes leading and trailing square brackets if present.
 * Adapted from https://github.com/chromium/chromium/blob/main/url/gurl.cc#L440-L448
 *
 * @param {string} host
 * @returns {string}
 */
const hostNoBrackets = (host) => {
  if (host.length >= 2 && host.startsWith('[') && host.endsWith(']')) {
    return host.substring(1, host.length - 1);
  }
  return host;
}

/**
 * Determines if a URL string represents a potentially trustworthy origin.
 * 
 * A URL is considered potentially trustworthy if it:
 * - Uses HTTPS, WSS or file schemes
 * - Points to a loopback address (IPv4 127.0.0.0/8 or IPv6 ::1)
 * - Uses localhost or *.localhost hostnames
 * 
 * @param {string} urlString - The URL to check
 * @returns {boolean}
 * @see {@link https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin W3C Spec}
 */
const isPotentiallyTrustworthyOrigin = (urlString) => {
  let url;

  // try ... catch doubles as an opaque origin check
  try {
    url = new URL(urlString);
  } catch (e) {
    if (e instanceof TypeError && e.code === 'ERR_INVALID_URL') {
      return false;
    } else throw e;
  }

  const scheme = url.protocol.replace(':', '').toLowerCase();
  const hostname = hostNoBrackets(
    url.hostname
  ).replace(/\.+$/, '');

  if (
    scheme === 'https' ||
    scheme === 'wss' ||
    scheme === 'file' // https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin
  ) {
    return true;
  }

  // If it's already an IP literal, check if it's a loopback address
  if (net.isIP(hostname)) {
    return isIpLoopback(hostname);
  }

  // RFC 6761 states that localhost names will always resolve
  // to the respective IP loopback address:
  // https://datatracker.ietf.org/doc/html/rfc6761#section-6.3
  return isLocalHostname(hostname);
}

module.exports = {
    isPotentiallyTrustworthyOrigin
};