| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| 'use strict'; |
| |
| const { |
| ObjectCreate, |
| ObjectDefineProperties, |
| ObjectDefineProperty, |
| } = primordials; |
| |
| const cares = internalBinding('cares_wrap'); |
| const { toASCII } = require('internal/idna'); |
| const { isIP, isLegalPort } = require('internal/net'); |
| const { customPromisifyArgs } = require('internal/util'); |
| const errors = require('internal/errors'); |
| const { |
| bindDefaultResolver, |
| getDefaultResolver, |
| setDefaultResolver, |
| Resolver, |
| validateHints, |
| emitInvalidHostnameWarning, |
| } = require('internal/dns/utils'); |
| const { |
| ERR_INVALID_ARG_TYPE, |
| ERR_INVALID_CALLBACK, |
| ERR_INVALID_OPT_VALUE, |
| ERR_MISSING_ARGS, |
| ERR_SOCKET_BAD_PORT |
| } = errors.codes; |
| const { validateString } = require('internal/validators'); |
| |
| const { |
| GetAddrInfoReqWrap, |
| GetNameInfoReqWrap, |
| QueryReqWrap, |
| } = cares; |
| |
| const dnsException = errors.dnsException; |
| |
| let promises = null; // Lazy loaded |
| |
| function onlookup(err, addresses) { |
| if (err) { |
| return this.callback(dnsException(err, 'getaddrinfo', this.hostname)); |
| } |
| if (this.family) { |
| this.callback(null, addresses[0], this.family); |
| } else { |
| this.callback(null, addresses[0], isIP(addresses[0])); |
| } |
| } |
| |
| |
| function onlookupall(err, addresses) { |
| if (err) { |
| return this.callback(dnsException(err, 'getaddrinfo', this.hostname)); |
| } |
| |
| const family = this.family; |
| for (let i = 0; i < addresses.length; i++) { |
| const addr = addresses[i]; |
| addresses[i] = { |
| address: addr, |
| family: family || isIP(addr) |
| }; |
| } |
| |
| this.callback(null, addresses); |
| } |
| |
| |
| // Easy DNS A/AAAA look up |
| // lookup(hostname, [options,] callback) |
| function lookup(hostname, options, callback) { |
| let hints = 0; |
| let family = -1; |
| let all = false; |
| let verbatim = false; |
| |
| // Parse arguments |
| if (hostname && typeof hostname !== 'string') { |
| throw new ERR_INVALID_ARG_TYPE('hostname', 'string', hostname); |
| } else if (typeof options === 'function') { |
| callback = options; |
| family = 0; |
| } else if (typeof callback !== 'function') { |
| throw new ERR_INVALID_CALLBACK(callback); |
| } 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); |
| |
| if (!hostname) { |
| emitInvalidHostnameWarning(hostname); |
| if (all) { |
| process.nextTick(callback, null, []); |
| } else { |
| process.nextTick(callback, null, null, family === 6 ? 6 : 4); |
| } |
| return {}; |
| } |
| |
| const matchedFamily = isIP(hostname); |
| if (matchedFamily) { |
| if (all) { |
| process.nextTick( |
| callback, null, [{ address: hostname, family: matchedFamily }]); |
| } else { |
| process.nextTick(callback, null, hostname, matchedFamily); |
| } |
| return {}; |
| } |
| |
| const req = new GetAddrInfoReqWrap(); |
| req.callback = callback; |
| req.family = family; |
| req.hostname = hostname; |
| req.oncomplete = all ? onlookupall : onlookup; |
| |
| const err = cares.getaddrinfo( |
| req, toASCII(hostname), family, hints, verbatim |
| ); |
| if (err) { |
| process.nextTick(callback, dnsException(err, 'getaddrinfo', hostname)); |
| return {}; |
| } |
| return req; |
| } |
| |
| ObjectDefineProperty(lookup, customPromisifyArgs, |
| { value: ['address', 'family'], enumerable: false }); |
| |
| |
| function onlookupservice(err, hostname, service) { |
| if (err) |
| return this.callback(dnsException(err, 'getnameinfo', this.hostname)); |
| |
| this.callback(null, hostname, service); |
| } |
| |
| |
| function lookupService(address, port, callback) { |
| if (arguments.length !== 3) |
| throw new ERR_MISSING_ARGS('address', 'port', 'callback'); |
| |
| if (isIP(address) === 0) |
| throw new ERR_INVALID_OPT_VALUE('address', address); |
| |
| if (!isLegalPort(port)) |
| throw new ERR_SOCKET_BAD_PORT(port); |
| |
| if (typeof callback !== 'function') |
| throw new ERR_INVALID_CALLBACK(callback); |
| |
| port = +port; |
| |
| const req = new GetNameInfoReqWrap(); |
| req.callback = callback; |
| req.hostname = address; |
| req.port = port; |
| req.oncomplete = onlookupservice; |
| |
| const err = cares.getnameinfo(req, address, port); |
| if (err) throw dnsException(err, 'getnameinfo', address); |
| return req; |
| } |
| |
| ObjectDefineProperty(lookupService, customPromisifyArgs, |
| { value: ['hostname', 'service'], enumerable: false }); |
| |
| |
| function onresolve(err, result, ttls) { |
| if (ttls && this.ttl) |
| result = result.map((address, index) => ({ address, ttl: ttls[index] })); |
| |
| if (err) |
| this.callback(dnsException(err, this.bindingName, this.hostname)); |
| else |
| this.callback(null, result); |
| } |
| |
| function resolver(bindingName) { |
| function query(name, /* options, */ callback) { |
| let options; |
| if (arguments.length > 2) { |
| options = callback; |
| callback = arguments[2]; |
| } |
| |
| validateString(name, 'name'); |
| if (typeof callback !== 'function') { |
| throw new ERR_INVALID_CALLBACK(callback); |
| } |
| |
| const req = new QueryReqWrap(); |
| req.bindingName = bindingName; |
| req.callback = callback; |
| req.hostname = name; |
| req.oncomplete = onresolve; |
| req.ttl = !!(options && options.ttl); |
| const err = this._handle[bindingName](req, toASCII(name)); |
| if (err) throw dnsException(err, bindingName, name); |
| return req; |
| } |
| ObjectDefineProperty(query, 'name', { value: bindingName }); |
| return query; |
| } |
| |
| const resolveMap = ObjectCreate(null); |
| 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 = resolve; |
| |
| function resolve(hostname, rrtype, callback) { |
| let resolver; |
| if (typeof rrtype === 'string') { |
| resolver = resolveMap[rrtype]; |
| } else if (typeof rrtype === 'function') { |
| resolver = resolveMap.A; |
| callback = rrtype; |
| } else { |
| throw new ERR_INVALID_ARG_TYPE('rrtype', 'string', rrtype); |
| } |
| |
| if (typeof resolver === 'function') { |
| return resolver.call(this, hostname, callback); |
| } else { |
| throw new ERR_INVALID_OPT_VALUE('rrtype', rrtype); |
| } |
| } |
| |
| function defaultResolverSetServers(servers) { |
| const resolver = new Resolver(); |
| |
| resolver.setServers(servers); |
| setDefaultResolver(resolver); |
| bindDefaultResolver(module.exports, Resolver.prototype); |
| |
| if (promises !== null) |
| bindDefaultResolver(promises, promises.Resolver.prototype); |
| } |
| |
| module.exports = { |
| lookup, |
| lookupService, |
| |
| Resolver, |
| setServers: defaultResolverSetServers, |
| |
| // uv_getaddrinfo flags |
| ADDRCONFIG: cares.AI_ADDRCONFIG, |
| V4MAPPED: cares.AI_V4MAPPED, |
| |
| // ERROR CODES |
| NODATA: 'ENODATA', |
| FORMERR: 'EFORMERR', |
| SERVFAIL: 'ESERVFAIL', |
| NOTFOUND: 'ENOTFOUND', |
| NOTIMP: 'ENOTIMP', |
| REFUSED: 'EREFUSED', |
| BADQUERY: 'EBADQUERY', |
| BADNAME: 'EBADNAME', |
| BADFAMILY: 'EBADFAMILY', |
| BADRESP: 'EBADRESP', |
| CONNREFUSED: 'ECONNREFUSED', |
| TIMEOUT: 'ETIMEOUT', |
| EOF: 'EOF', |
| FILE: 'EFILE', |
| NOMEM: 'ENOMEM', |
| DESTRUCTION: 'EDESTRUCTION', |
| BADSTR: 'EBADSTR', |
| BADFLAGS: 'EBADFLAGS', |
| NONAME: 'ENONAME', |
| BADHINTS: 'EBADHINTS', |
| NOTINITIALIZED: 'ENOTINITIALIZED', |
| LOADIPHLPAPI: 'ELOADIPHLPAPI', |
| ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS', |
| CANCELLED: 'ECANCELLED' |
| }; |
| |
| bindDefaultResolver(module.exports, getDefaultResolver()); |
| |
| ObjectDefineProperties(module.exports, { |
| promises: { |
| configurable: true, |
| enumerable: true, |
| get() { |
| if (promises === null) { |
| promises = require('internal/dns/promises'); |
| promises.setServers = defaultResolverSetServers; |
| } |
| return promises; |
| } |
| } |
| }); |