mirror of
https://github.com/nodejs/node.git
synced 2025-05-21 08:27:49 +00:00

Before this commit, Node.js left it up to the system resolver or c-ares. Leaving it to the system resolver introduces platform differences because: * some support IDNA 2008 * some only IDNA 2003 (glibc until 2.28), and * some don't support IDNA at all (musl libc) c-ares doesn't support IDNA either although curl does, by virtue of linking against libidn2. Upgrading from libidn1 to libidn2 in order to get proper IDNA 2008 support was the fix for curl's CVE-2016-8625. libidn2 is not an option (incompatible license) but ICU has an IDNA API and we already use that in one place. For non-ICU builds, we fall back to the bundled punycode.js that also supports IDNA 2008. Fixes: https://github.com/nodejs-private/security/issues/97 Fixes: https://github.com/nodejs/node/issues/25558 PR-URL: https://github.com/nodejs/node/pull/25679 Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com> Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
251 lines
6.5 KiB
JavaScript
251 lines
6.5 KiB
JavaScript
'use strict';
|
|
const {
|
|
bindDefaultResolver,
|
|
Resolver: CallbackResolver,
|
|
validateHints,
|
|
emitInvalidHostnameWarning,
|
|
} = require('internal/dns/utils');
|
|
const { codes, dnsException } = require('internal/errors');
|
|
const { toASCII } = require('internal/idna');
|
|
const { isIP, isIPv4, isLegalPort } = require('internal/net');
|
|
const {
|
|
getaddrinfo,
|
|
getnameinfo,
|
|
ChannelWrap,
|
|
GetAddrInfoReqWrap,
|
|
GetNameInfoReqWrap,
|
|
QueryReqWrap
|
|
} = internalBinding('cares_wrap');
|
|
const {
|
|
ERR_INVALID_ARG_TYPE,
|
|
ERR_INVALID_OPT_VALUE,
|
|
ERR_MISSING_ARGS,
|
|
ERR_SOCKET_BAD_PORT
|
|
} = codes;
|
|
const { validateString } = require('internal/validators');
|
|
|
|
|
|
function onlookup(err, addresses) {
|
|
if (err) {
|
|
this.reject(dnsException(err, 'getaddrinfo', this.hostname));
|
|
return;
|
|
}
|
|
|
|
const family = this.family ? this.family : isIPv4(addresses[0]) ? 4 : 6;
|
|
this.resolve({ address: addresses[0], family });
|
|
}
|
|
|
|
function onlookupall(err, addresses) {
|
|
if (err) {
|
|
this.reject(dnsException(err, 'getaddrinfo', this.hostname));
|
|
return;
|
|
}
|
|
|
|
const family = this.family;
|
|
|
|
for (var i = 0; i < addresses.length; i++) {
|
|
const address = addresses[i];
|
|
|
|
addresses[i] = {
|
|
address,
|
|
family: family ? family : isIPv4(addresses[i]) ? 4 : 6
|
|
};
|
|
}
|
|
|
|
this.resolve(addresses);
|
|
}
|
|
|
|
function createLookupPromise(family, hostname, all, hints, verbatim) {
|
|
return new Promise((resolve, reject) => {
|
|
if (!hostname) {
|
|
emitInvalidHostnameWarning(hostname);
|
|
if (all)
|
|
resolve([]);
|
|
else
|
|
resolve({ address: null, family: family === 6 ? 6 : 4 });
|
|
|
|
return;
|
|
}
|
|
|
|
const matchedFamily = isIP(hostname);
|
|
|
|
if (matchedFamily !== 0) {
|
|
const result = { address: hostname, family: matchedFamily };
|
|
if (all)
|
|
resolve([result]);
|
|
else
|
|
resolve(result);
|
|
|
|
return;
|
|
}
|
|
|
|
const req = new GetAddrInfoReqWrap();
|
|
|
|
req.family = family;
|
|
req.hostname = hostname;
|
|
req.oncomplete = all ? onlookupall : onlookup;
|
|
req.resolve = resolve;
|
|
req.reject = reject;
|
|
|
|
const err = getaddrinfo(req, toASCII(hostname), family, hints, verbatim);
|
|
|
|
if (err) {
|
|
reject(dnsException(err, 'getaddrinfo', hostname));
|
|
}
|
|
});
|
|
}
|
|
|
|
function lookup(hostname, options) {
|
|
var hints = 0;
|
|
var family = -1;
|
|
var all = false;
|
|
var verbatim = false;
|
|
|
|
// Parse arguments
|
|
if (hostname && typeof hostname !== 'string') {
|
|
throw new ERR_INVALID_ARG_TYPE('hostname', 'string', hostname);
|
|
} else if (options !== null && typeof options === 'object') {
|
|
hints = options.hints >>> 0;
|
|
family = options.family >>> 0;
|
|
all = options.all === true;
|
|
verbatim = options.verbatim === true;
|
|
|
|
validateHints(hints);
|
|
} else {
|
|
family = options >>> 0;
|
|
}
|
|
|
|
if (family !== 0 && family !== 4 && family !== 6)
|
|
throw new ERR_INVALID_OPT_VALUE('family', family);
|
|
|
|
return createLookupPromise(family, hostname, all, hints, verbatim);
|
|
}
|
|
|
|
|
|
function onlookupservice(err, hostname, service) {
|
|
if (err) {
|
|
this.reject(dnsException(err, 'getnameinfo', this.host));
|
|
return;
|
|
}
|
|
|
|
this.resolve({ hostname, service });
|
|
}
|
|
|
|
function createLookupServicePromise(hostname, port) {
|
|
return new Promise((resolve, reject) => {
|
|
const req = new GetNameInfoReqWrap();
|
|
|
|
req.hostname = hostname;
|
|
req.port = port;
|
|
req.oncomplete = onlookupservice;
|
|
req.resolve = resolve;
|
|
req.reject = reject;
|
|
|
|
const err = getnameinfo(req, hostname, port);
|
|
|
|
if (err)
|
|
reject(dnsException(err, 'getnameinfo', hostname));
|
|
});
|
|
}
|
|
|
|
function lookupService(hostname, port) {
|
|
if (arguments.length !== 2)
|
|
throw new ERR_MISSING_ARGS('hostname', 'port');
|
|
|
|
if (isIP(hostname) === 0)
|
|
throw new ERR_INVALID_OPT_VALUE('hostname', hostname);
|
|
|
|
if (!isLegalPort(port))
|
|
throw new ERR_SOCKET_BAD_PORT(port);
|
|
|
|
return createLookupServicePromise(hostname, +port);
|
|
}
|
|
|
|
|
|
function onresolve(err, result, ttls) {
|
|
if (err) {
|
|
this.reject(dnsException(err, this.bindingName, this.hostname));
|
|
return;
|
|
}
|
|
|
|
if (ttls && this.ttl)
|
|
result = result.map((address, index) => ({ address, ttl: ttls[index] }));
|
|
|
|
this.resolve(result);
|
|
}
|
|
|
|
function createResolverPromise(resolver, bindingName, hostname, ttl) {
|
|
return new Promise((resolve, reject) => {
|
|
const req = new QueryReqWrap();
|
|
|
|
req.bindingName = bindingName;
|
|
req.hostname = hostname;
|
|
req.oncomplete = onresolve;
|
|
req.resolve = resolve;
|
|
req.reject = reject;
|
|
req.ttl = ttl;
|
|
|
|
const err = resolver._handle[bindingName](req, toASCII(hostname));
|
|
|
|
if (err)
|
|
reject(dnsException(err, bindingName, hostname));
|
|
});
|
|
}
|
|
|
|
function resolver(bindingName) {
|
|
function query(name, options) {
|
|
validateString(name, 'name');
|
|
|
|
const ttl = !!(options && options.ttl);
|
|
return createResolverPromise(this, bindingName, name, ttl);
|
|
}
|
|
|
|
Object.defineProperty(query, 'name', { value: bindingName });
|
|
return query;
|
|
}
|
|
|
|
|
|
const resolveMap = Object.create(null);
|
|
|
|
// Resolver instances correspond 1:1 to c-ares channels.
|
|
class Resolver {
|
|
constructor() {
|
|
this._handle = new ChannelWrap();
|
|
}
|
|
}
|
|
|
|
Resolver.prototype.getServers = CallbackResolver.prototype.getServers;
|
|
Resolver.prototype.setServers = CallbackResolver.prototype.setServers;
|
|
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
|
|
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
|
|
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
|
|
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
|
|
Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
|
|
Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
|
|
Resolver.prototype.resolveTxt = resolveMap.TXT = resolver('queryTxt');
|
|
Resolver.prototype.resolveSrv = resolveMap.SRV = resolver('querySrv');
|
|
Resolver.prototype.resolvePtr = resolveMap.PTR = resolver('queryPtr');
|
|
Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
|
|
Resolver.prototype.resolveSoa = resolveMap.SOA = resolver('querySoa');
|
|
Resolver.prototype.reverse = resolver('getHostByAddr');
|
|
Resolver.prototype.resolve = function resolve(hostname, rrtype) {
|
|
var resolver;
|
|
|
|
if (typeof rrtype === 'string') {
|
|
resolver = resolveMap[rrtype];
|
|
|
|
if (typeof resolver !== 'function')
|
|
throw new ERR_INVALID_OPT_VALUE('rrtype', rrtype);
|
|
} else if (rrtype === undefined) {
|
|
resolver = resolveMap.A;
|
|
} else {
|
|
throw new ERR_INVALID_ARG_TYPE('rrtype', 'string', rrtype);
|
|
}
|
|
|
|
return resolver.call(this, hostname);
|
|
};
|
|
|
|
|
|
module.exports = { lookup, lookupService, Resolver };
|
|
bindDefaultResolver(module.exports, Resolver.prototype);
|