From b2e5cfe9d2f6f83dcb984746c60e894e2fab33b6 Mon Sep 17 00:00:00 2001 From: Daniel Aleksandersen Date: Wed, 25 Mar 2020 20:02:59 +0100 Subject: [PATCH] Add redirect support (limit: 6) Resolves issue #25. --- README.md | 3 +++ index.js | 48 ++++++++++++++++++++++++++++++++++++++---------- test.js | 36 ++++++++++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e8f1bc2..2f6747e 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ datDns.resolveName('foo.com', {noDnsOverHttps: true}) // dont use .well-known/dat datDns.resolveName('foo.com', {noWellknownDat: true}) +// specify amount of redirects (default: 7) +datDns.resolveName('foo.com', { followRedirects: 2 }) + // list all entries in the cache datDns.listCache() diff --git a/index.js b/index.js index f516642..c083209 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,7 @@ const VERSION_REGEX = /(\+[^\/]+)$/ const DEFAULT_DAT_DNS_TTL = 3600 // 1hr const MAX_DAT_DNS_TTL = 3600 * 24 * 7 // 1 week const DEFAULT_DNS_PROVIDERS = [['cloudflare-dns.com', 443, '/dns-query'], ['dns.google', 443, '/resolve']] +const DEFAULT_FOLLOW_REDIRECTS = 6 module.exports = createDatDNS @@ -69,6 +70,7 @@ function createDatDNS (datDnsOpts) { var ignoreCachedMiss = opts && opts.ignoreCachedMiss var noDnsOverHttps = opts && opts.noDnsOverHttps var noWellknownDat = opts && opts.noWellknownDat + var followRedirects = (opts && opts.followRedirects) || DEFAULT_FOLLOW_REDIRECTS return maybe(cb, _asyncToGenerator(function * () { // parse the name as needed var nameParsed = url.parse(name) @@ -117,7 +119,10 @@ function createDatDNS (datDnsOpts) { if (!res && !noWellknownDat) { // do a .well-known/`${recordName}` lookup - res = yield fetchWellKnownRecord(name, recordName) + const wellknownPath = !recordName.startsWith('/') + ? '/.well-known/' + recordName + : recordName + res = yield fetchWellKnownRecordWithRedirects(recordName, name, wellknownPath, followRedirects) if (res.statusCode === 0 || res.statusCode === 404) { debug('.well-known/' + recordName + ' lookup failed for name:', name, res.statusCode, res.err) datDns.emit('failed', { @@ -280,16 +285,39 @@ function parseDnsOverHttpsRecord (datDns, name, body, dnsTxtRegex) { return res } -function fetchWellKnownRecord (name, recordName) { - return new Promise((resolve, reject) => { - debug('.well-known/dat lookup for name:', name) - https.get({ - host: name, - path: '/.well-known/' + recordName, - timeout: 2000 - }, function (res) { +function fetchWellKnownRecordWithRedirects (recordName, host, path, followRedirects) { + return _asyncToGenerator(function * () { + let redirectCount = 0 + while (redirectCount < followRedirects) { + const res = yield fetchWellKnownRecord(recordName, host, path) + if ([301, 302, 307, 308].includes(res.statusCode)) { + if (!'location' in res.headers) { + debug('.well-known/' + recordName + ' lookup redirect did not contain destination Location header.') + throw new Error('Well record redirected to nowhere') + } + // resolve relative paths with original URL as base URL + const uri = new URL(res.headers['location'], 'https://' + host) + if (uri.protocol !== 'https:') { + throw new Error('DNS record redirected to non https: protocol: ' + uri.href) + } + host = uri.host + path = uri.pathname + uri.search + redirectCount++ + debug('.well-known/' + recordName + ' lookup redirected to https://' + host + path, '(' + res.statusCode + ') [' + redirectCount + '/' + followRedirects + ']') + } else { + return res + } + } + throw new Error('Well known record lookup exceeded redirection limit: ' + followRedirects) + })() +} + +function fetchWellKnownRecord (recordName, host, path) { + return new Promise(resolve => { + debug('.well-known/' + recordName + ' lookup at https://' + host + path) + https.get({ host, path, timeout: 2000 }, function (res) { res.setEncoding('utf-8') - res.pipe(concat(body => resolve({ statusCode: res.statusCode, body }))) + res.pipe(concat(body => resolve({ statusCode: res.statusCode, headers: res.headers, body }))) }).on('error', function (err) { resolve({ statusCode: 0, err, body: '' }) }) diff --git a/test.js b/test.js index 6701a10..9620009 100644 --- a/test.js +++ b/test.js @@ -8,8 +8,6 @@ var cabalDns = createDatDNS({ txtRegex: /^"?cabalkey=([0-9a-f]{64})"?$/i }) -var FAKE_DAT = 'f'.repeat(64) - tape('Successful test against cblgh.org', function (t) { cabalDns.resolveName('cblgh.org', function (err, name) { t.error(err) @@ -197,6 +195,40 @@ tape('Successful test against dns-test-setup.dat-ecosystem.org (no well-known/da }) }) +tape('Successful test against dns-test-setup-2.dat-ecosystem.org (well-known, multiple, , redirects)', function (t) { + const datDnsHop3 = require('./index')({ recordName: 'dat-hop-3' }) + datDnsHop3.resolveName('dns-test-setup-2.dat-ecosystem.org', {noDnsOverHttps: true, ignoreCache: true }) + .then(name => { + t.equals(name, '222231b5589a5099aa3610a8ee550dcd454c3e33f4cac93b7d41b6b850cde222') + return datDnsHop3.resolveName('dns-test-setup-2.dat-ecosystem.org') + .then(name2 => { + t.equal(name, name2) + t.end() + }) + }) + .catch(err => { + t.error(err) + t.end() + }) +}) + +tape('Fail test against dns-test-setup-2.dat-ecosystem.org (well-known, multiple, exceeding redirects)', function (t) { + const datDnsHop3 = require('./index')({ recordName: 'dat-hop-3' }) + datDnsHop3.resolveName('dns-test-setup-2.dat-ecosystem.org', {noDnsOverHttps: true, ignoreCache: true, followRedirects: 2}) + .then(name => { + t.equals(name, '222231b5589a5099aa3610a8ee550dcd454c3e33f4cac93b7d41b6b850cde222') + return datDnsHop3.resolveName('dns-test-setup-2.dat-ecosystem.org') + .then(() => { + t.fail('Dont expect to succeed') + t.end() + }) + }) + .catch(err => { + t.equals(err.message, 'Well known record lookup exceeded redirection limit: 2') + t.end() + }) +}) + tape('List cache', function (t) { t.is(Object.keys(datDns.listCache()).length, 6) t.end()