enclose-io/compiler

View on GitHub
lts/lib/internal/dns/utils.js

Summary

Maintainability
F
4 days
Test Coverage
'use strict';

const {
  ArrayIsArray,
} = primordials;

const errors = require('internal/errors');
const { isIP } = require('internal/net');
const { validateInt32 } = require('internal/validators');
const {
  ChannelWrap,
  strerror,
  AI_ADDRCONFIG,
  AI_ALL,
  AI_V4MAPPED,
} = internalBinding('cares_wrap');
const IANA_DNS_PORT = 53;
const IPv6RE = /^\[([^[\]]*)\]/;
const addrSplitRE = /(^.+?)(?::(\d+))?$/;
const {
  ERR_DNS_SET_SERVERS_FAILED,
  ERR_INVALID_ARG_TYPE,
  ERR_INVALID_IP_ADDRESS,
  ERR_INVALID_OPT_VALUE
} = errors.codes;

function validateTimeout(options) {
  const { timeout = -1 } = { ...options };
  validateInt32(timeout, 'options.timeout', -1, 2 ** 31 - 1);
  return timeout;
}

// Resolver instances correspond 1:1 to c-ares channels.
class Resolver {
  constructor(options = undefined) {
    const timeout = validateTimeout(options);
    this._handle = new ChannelWrap(timeout);
  }

  cancel() {
    this._handle.cancel();
  }

  getServers() {
    return this._handle.getServers().map((val) => {
      if (!val[1] || val[1] === IANA_DNS_PORT)
        return val[0];

      const host = isIP(val[0]) === 6 ? `[${val[0]}]` : val[0];
      return `${host}:${val[1]}`;
    });
  }

  setServers(servers) {
    if (!ArrayIsArray(servers)) {
      throw new ERR_INVALID_ARG_TYPE('servers', 'Array', servers);
    }

    // Cache the original servers because in the event of an error while
    // setting the servers, c-ares won't have any servers available for
    // resolution.
    const orig = this._handle.getServers();
    const newSet = [];

    servers.forEach((serv, index) => {
      if (typeof serv !== 'string') {
        throw new ERR_INVALID_ARG_TYPE(`servers[${index}]`, 'string', serv);
      }
      let ipVersion = isIP(serv);

      if (ipVersion !== 0)
        return newSet.push([ipVersion, serv, IANA_DNS_PORT]);

      const match = serv.match(IPv6RE);

      // Check for an IPv6 in brackets.
      if (match) {
        ipVersion = isIP(match[1]);

        if (ipVersion !== 0) {
          const port =
            parseInt(serv.replace(addrSplitRE, '$2')) || IANA_DNS_PORT;
          return newSet.push([ipVersion, match[1], port]);
        }
      }

      // addr::port
      const addrSplitMatch = serv.match(addrSplitRE);

      if (addrSplitMatch) {
        const hostIP = addrSplitMatch[1];
        const port = addrSplitMatch[2] || IANA_DNS_PORT;

        ipVersion = isIP(hostIP);

        if (ipVersion !== 0) {
          return newSet.push([ipVersion, hostIP, parseInt(port)]);
        }
      }

      throw new ERR_INVALID_IP_ADDRESS(serv);
    });

    const errorNumber = this._handle.setServers(newSet);

    if (errorNumber !== 0) {
      // Reset the servers to the old servers, because ares probably unset them.
      this._handle.setServers(orig.join(','));
      const err = strerror(errorNumber);
      throw new ERR_DNS_SET_SERVERS_FAILED(err, servers);
    }
  }
}

let defaultResolver = new Resolver();
const resolverKeys = [
  'getServers',
  'resolve',
  'resolve4',
  'resolve6',
  'resolveAny',
  'resolveCname',
  'resolveMx',
  'resolveNaptr',
  'resolveNs',
  'resolvePtr',
  'resolveSoa',
  'resolveSrv',
  'resolveTxt',
  'reverse',
];

function getDefaultResolver() {
  return defaultResolver;
}

function setDefaultResolver(resolver) {
  defaultResolver = resolver;
}

function bindDefaultResolver(target, source) {
  resolverKeys.forEach((key) => {
    target[key] = source[key].bind(defaultResolver);
  });
}

function validateHints(hints) {
  if ((hints & ~(AI_ADDRCONFIG | AI_ALL | AI_V4MAPPED)) !== 0) {
    throw new ERR_INVALID_OPT_VALUE('hints', hints);
  }
}

let invalidHostnameWarningEmitted = false;

function emitInvalidHostnameWarning(hostname) {
  if (invalidHostnameWarningEmitted) {
    return;
  }
  invalidHostnameWarningEmitted = true;
  process.emitWarning(
    `The provided hostname "${hostname}" is not a valid ` +
    'hostname, and is supported in the dns module solely for compatibility.',
    'DeprecationWarning',
    'DEP0118'
  );
}

module.exports = {
  bindDefaultResolver,
  getDefaultResolver,
  setDefaultResolver,
  validateHints,
  validateTimeout,
  Resolver,
  emitInvalidHostnameWarning,
};