Comparing version 1.4.5 to 1.4.6
181
index.js
@@ -10,4 +10,7 @@ const dns = require('node:dns'); | ||
const { toASCII } = require('punycode/'); | ||
const autoBind = require('auto-bind'); | ||
const getStream = require('get-stream'); | ||
const hostile = require('hostile'); | ||
const ipaddr = require('ipaddr.js'); | ||
@@ -21,5 +24,4 @@ const mergeOptions = require('merge-options'); | ||
const structuredClone = require('@ungap/structured-clone').default; | ||
const { Hosts } = require('hosts-parser'); | ||
const { getService } = require('port-numbers'); | ||
// eslint-disable-next-line import/order | ||
const { toASCII } = require('punycode/'); | ||
@@ -30,2 +32,9 @@ const pkg = require('./package.json'); | ||
const hosts = new Hosts( | ||
hostile | ||
.get() | ||
.map((arr) => arr.join(' ')) | ||
.join('\n') | ||
); | ||
// dynamically import dohdec | ||
@@ -154,3 +163,4 @@ let dohdec; | ||
dns.SERVFAIL, | ||
dns.TIMEOUT | ||
dns.TIMEOUT, | ||
'EINVAL' | ||
]); | ||
@@ -646,39 +656,112 @@ | ||
// <https://github.com/c-ares/c-ares/blob/38b30bc922c21faa156939bde15ea35332c30e08/src/lib/ares_getaddrinfo.c#L407> | ||
// <https://www.rfc-editor.org/rfc/rfc6761.html#section-6.3> | ||
// | ||
// > 'localhost and any domains falling within .localhost' | ||
// | ||
// if no system loopback match, then revert to the default | ||
// <https://github.com/c-ares/c-ares/blob/38b30bc922c21faa156939bde15ea35332c30e08/src/lib/ares__addrinfo_localhost.c#L224-L229> | ||
// - IPv4 = '127.0.0.1" | ||
// - IPv6 = "::1" | ||
// | ||
let resolve4; | ||
let resolve6; | ||
// sorted in reverse to match behavior of lookup | ||
for (const rule of hosts._origin.reverse()) { | ||
if ( | ||
rule.hostname.toLowerCase() !== name.toLowerCase() && | ||
rule.ip !== name | ||
) | ||
continue; | ||
const type = isIP(rule.ip); | ||
if (!resolve4 && type === 4) resolve4 = [rule.ip]; | ||
else if (!resolve6 && type === 6) resolve6 = [rule.ip]; | ||
if (resolve4 && resolve6) break; | ||
} | ||
// if no matches found for resolve4 and resolve6 and it was localhost | ||
// (this is a safeguard in case host file is missing these) | ||
if ( | ||
name.toLowerCase() === 'localhost' || | ||
name.toLowerCase() === 'localhost.' | ||
) { | ||
if (!resolve4) resolve4 = ['127.0.0.1']; | ||
if (!resolve6) resolve6 = ['::1']; | ||
} | ||
if (isIPv4(name)) { | ||
resolve4 = [name]; | ||
resolve6 = []; | ||
} else if (isIPv6(name)) { | ||
resolve6 = [name]; | ||
resolve4 = []; | ||
} | ||
// resolve the first A or AAAA record (conditionally) | ||
const results = await Promise.all( | ||
[ | ||
Array.isArray(resolve4) | ||
? Promise.resolve(resolve4) | ||
: this.resolve4(name, { purgeCache, noThrowOnNODATA: true }), | ||
Array.isArray(resolve6) | ||
? Promise.resolve(resolve6) | ||
: this.resolve6(name, { purgeCache, noThrowOnNODATA: true }) | ||
].map((p) => p.catch((err) => err)) | ||
); | ||
const errors = []; | ||
let answers = []; | ||
try { | ||
answers = await Promise.all([ | ||
this.resolve4(name, { purgeCache, noThrowOnNODATA: true }), | ||
this.resolve6(name, { purgeCache, noThrowOnNODATA: true }) | ||
]); | ||
// default node behavior seems to return IPv4 by default always regardless | ||
answers = | ||
answers[0].length > 0 && | ||
(typeof options.family === 'undefined' || options.family === 0) | ||
? answers[0] | ||
: answers.flat(); | ||
} catch (_err) { | ||
debug(_err); | ||
for (const result of results) { | ||
if (result instanceof Error) { | ||
errors.push(result); | ||
} else { | ||
answers.push(result); | ||
} | ||
} | ||
// this will most likely be instanceof AggregateError | ||
if (_err instanceof AggregateError) { | ||
const err = this.constructor.combineErrors(_err.errors); | ||
if ( | ||
answers.length === 0 && | ||
errors.length > 0 && | ||
errors.every((e) => e.code === errors[0].code) | ||
) { | ||
const err = this.constructor.createError( | ||
name, | ||
'', | ||
errors[0].code === dns.BADNAME ? dns.NOTFOUND : errors[0].code | ||
); | ||
// remap and perform syscall | ||
err.syscall = 'getaddrinfo'; | ||
err.message = err.message.replace('query', 'getaddrinfo'); | ||
err.errno = -3008; | ||
throw err; | ||
} | ||
/* | ||
// | ||
// NOTE: we probably should handle this differently (?) | ||
// (not sure what native nodejs dns module does for different errors - haven't checked yet) | ||
// | ||
if (errors.every((e) => e.code !== 'ENODATA')) { | ||
const err = this.constructor.combineErrors(errors); | ||
err.hostname = name; | ||
// remap and perform syscall | ||
err.syscall = 'getaddrinfo'; | ||
err.message = err.message.replace('query', 'getaddrinfo'); | ||
if (!err.code) | ||
err.code = _err.errors.find((e) => e.code)?.code || dns.BADRESP; | ||
err.code = errors.find((e) => e.code)?.code || dns.BADRESP; | ||
if (!err.errno) | ||
err.errno = _err.errors.find((e) => e.errno)?.errno || undefined; | ||
err.errno = errors.find((e) => e.errno)?.errno || undefined; | ||
throw err; | ||
} | ||
*/ | ||
const err = this.constructor.createError(name, '', _err.code, _err.errno); | ||
// remap and perform syscall | ||
err.syscall = 'getaddrinfo'; | ||
err.error = _err; | ||
throw err; | ||
} | ||
// default node behavior seems to return IPv4 by default always regardless | ||
if (answers.length > 0) | ||
answers = | ||
answers[0].length > 0 && | ||
(typeof options.family === 'undefined' || options.family === 0) | ||
? answers[0] | ||
: answers.flat(); | ||
@@ -690,3 +773,4 @@ // if no results then throw ENODATA | ||
err.syscall = 'getaddrinfo'; | ||
// err.errno = -3008; | ||
err.message = err.message.replace('query', 'getaddrinfo'); | ||
err.errno = -3008; | ||
throw err; | ||
@@ -844,5 +928,6 @@ } | ||
if (!isIP(ip)) { | ||
const err = this.constructor.createError(ip, '', dns.EINVAL); | ||
const err = this.constructor.createError(ip, '', 'EINVAL'); | ||
err.message = `getHostByAddr EINVAL ${err.hostname}`; | ||
err.syscall = 'getHostByAddr'; | ||
// err.errno = -22; | ||
err.errno = -22; | ||
if (!ip) delete err.hostname; | ||
@@ -852,4 +937,8 @@ throw err; | ||
// edge case where localhost IP returns empty | ||
if (ip === '127.0.0.1' || ip === '::1') return []; | ||
// reverse the IP address | ||
if (!dohdec) await pWaitFor(() => Boolean(dohdec)); | ||
const name = dohdec.DNSoverHTTPS.reverse(ip); | ||
@@ -1281,9 +1370,18 @@ | ||
const results = await pMap( | ||
this.constructor.ANY_TYPES, | ||
this.#resolveByType(name, options, abortController), | ||
// <https://developers.cloudflare.com/fundamentals/api/reference/limits/> | ||
{ concurrency: this.options.concurrency, signal: abortController.signal } | ||
); | ||
return results.flat().filter(Boolean); | ||
try { | ||
const results = await pMap( | ||
this.constructor.ANY_TYPES, | ||
this.#resolveByType(name, options, abortController), | ||
// <https://developers.cloudflare.com/fundamentals/api/reference/limits/> | ||
{ | ||
concurrency: this.options.concurrency, | ||
signal: abortController.signal | ||
} | ||
); | ||
return results.flat().filter(Boolean); | ||
} catch (err) { | ||
err.syscall = 'queryAny'; | ||
err.message = `queryAny ${err.code} ${name}`; | ||
throw err; | ||
} | ||
} | ||
@@ -1342,2 +1440,7 @@ | ||
// edge case where c-ares detects "." as start of string | ||
// <https://github.com/c-ares/c-ares/blob/38b30bc922c21faa156939bde15ea35332c30e08/src/lib/ares_getaddrinfo.c#L829> | ||
if (name.startsWith('.') || name.includes('..')) | ||
throw this.constructor.createError(name, rrtype, dns.BADNAME); | ||
// purge cache support | ||
@@ -1725,3 +1828,3 @@ let purgeCache; | ||
} catch (err) { | ||
console.error(err); | ||
this.options.logger.error(err, { name, rrtype, options, answer }); | ||
throw err; | ||
@@ -1728,0 +1831,0 @@ } |
{ | ||
"name": "tangerine", | ||
"description": "Tangerine is the best Node.js drop-in replacement for dns.promises.Resolver using DNS over HTTPS (\"DoH\") via undici with built-in retries, timeouts, smart server rotation, AbortControllers, and caching support for multiple backends (with TTL and purge support).", | ||
"version": "1.4.5", | ||
"version": "1.4.6", | ||
"author": "Forward Email (https://forwardemail.net)", | ||
@@ -18,2 +18,4 @@ "bugs": { | ||
"get-stream": "6", | ||
"hostile": "^1.3.3", | ||
"hosts-parser": "^0.3.2", | ||
"ipaddr.js": "^2.0.1", | ||
@@ -20,0 +22,0 @@ "merge-options": "3.0.4", |
119169
1657
16
+ Addedhostile@^1.3.3
+ Addedhosts-parser@^0.3.2
+ Added@ljharb/through@2.3.14(transitive)
+ Added@types/node@22.13.1(transitive)
+ Addedansi-styles@4.3.0(transitive)
+ Addedcall-bind@1.0.8(transitive)
+ Addedcall-bind-apply-helpers@1.0.1(transitive)
+ Addedchalk@4.1.2(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addeddefine-data-property@1.1.4(transitive)
+ Addeddunder-proto@1.0.1(transitive)
+ Addedes-define-property@1.0.1(transitive)
+ Addedes-errors@1.3.0(transitive)
+ Addedes-object-atoms@1.1.1(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedget-intrinsic@1.2.7(transitive)
+ Addedget-proto@1.0.1(transitive)
+ Addedgopd@1.2.0(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedhas-property-descriptors@1.0.2(transitive)
+ Addedhas-symbols@1.1.0(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedhostile@1.4.0(transitive)
+ Addedhosts-parser@0.3.2(transitive)
+ Addedlodash@3.10.1(transitive)
+ Addedmath-intrinsics@1.1.0(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedset-function-length@1.2.2(transitive)
+ Addedsplit@1.0.1(transitive)
+ Addedsupports-color@7.2.0(transitive)
+ Addedthrough@2.3.8(transitive)
- Removed@types/node@22.13.4(transitive)