Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add redirect support (limit: 6) #27

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 59 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const DAT_TXT_REGEX = /"?datkey=([0-9a-f]{64})"?/i
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'], ['dns.quad9.net', 5053, '/dns-query'], ['doh.opendns.com', 443, 'dns-query']]
const DEFAULT_DNS_PROVIDERS = [['cloudflare-dns.com', 443, '/dns-query'], ['dns.google', 443, '/resolve'], ['dns.quad9.net', 5053, '/dns-query'], ['doh.opendns.com', 443, '/dns-query']]

// helper to support node6
function _asyncToGenerator (fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step (key, arg) { try { var info = gen[key](arg); var value = info.value } catch (error) { reject(error); return } if (info.done) { resolve(value) } else { return Promise.resolve(value).then(function (value) { step('next', value) }, function (err) { step('throw', err) }) } } return step('next') }) } }
Expand Down Expand Up @@ -115,24 +115,58 @@ module.exports = function (datDnsOpts) {

if (!res && !noWellknownDat) {
// do a .well-known/`${recordName}` lookup
res = yield fetchWellKnownRecord(name, recordName)
if (res.statusCode === 0 || res.statusCode === 404) {
debug('.well-known/' + recordName + ' lookup failed for name:', name, res.statusCode, res.err)
datDns.emit('failed', {
method: 'well-known',
name,
err: 'HTTP code ' + res.statusCode + ' ' + res.err
})
mCache.set(name, false, 60) // cache the miss for a minute
throw new Error('DNS record not found')
} else if (res.statusCode !== 200) {
debug('.well-known/' + recordName + ' lookup failed for name:', name, res.statusCode)
datDns.emit('failed', {
method: 'well-known',
name,
err: 'HTTP code ' + res.statusCode
})
throw new Error('DNS record not found')
let redirect_count = 0;
let fetch_well_known_host = name;
let fetch_well_known_path = recordName;

while (redirect_count < 7) {
res = yield fetchWellKnownRecord(fetch_well_known_host, fetch_well_known_path)
if (res.statusCode === 200) {
break;
} else if (res.statusCode === 0 || res.statusCode === 404) {
debug('.well-known/' + recordName + ' lookup failed for name:', name, res.statusCode, res.err)
datDns.emit('failed', {
method: 'well-known',
name,
err: 'HTTP code ' + res.statusCode + ' ' + res.err
})
mCache.set(name, false, 60) // cache the miss for a minute
throw new Error('DNS record error or not found')
} else if ([301, 302, 307, 308].includes(res.statusCode)) {
if (redirect_count >= 6)
{
debug('.well-known/' + recordName + ' lookup exceeded redirection limit', name, res.statusCode)
throw new Error('DNS record lookup exceeded redirection limit')
}

debug('.well-known/' + recordName + ' lookup redirected name:', name, res.statusCode)
if (!'location' in res.headers)
{
debug('.well-known/' + recordName + ' lookup redirect did not contain destination Location header:', name, res.statusCode)
throw new Error('DNS record redirected to nowhere')
}

// resolve relative paths with original URL as base URL
let redirect_uri = new URL(res.headers['location'], 'https://' + fetch_well_known_host);
if (redirect_uri.protocol != 'https:')
{
debug('.well-known/' + recordName + ' lookup redirected name to protocol other than https:', name, res.statusCode)
throw new Error('DNS record redirected to non https: protocol')
}
fetch_well_known_host = redirect_uri.host
fetch_well_known_path = redirect_uri.pathname + redirect_uri.search

redirect_count++;
continue;
} else if (res.statusCode !== 200) {
debug('.well-known/' + recordName + ' lookup failed for name:', name, res.statusCode)
datDns.emit('failed', {
method: 'well-known',
name,
err: 'HTTP code ' + res.statusCode
})
throw new Error('DNS record did not return OK status')
}
}

// parse the record
Expand Down Expand Up @@ -276,13 +310,17 @@ function parseDnsOverHttpsRecord (datDns, name, body, dnsTxtRegex) {
function fetchWellKnownRecord (name, recordName) {
return new Promise((resolve, reject) => {
debug('.well-known/dat lookup for name:', name)
if (!recordName.startsWith('/'))
{
recordName = '/.well-known/' + recordName
}
https.get({
host: name,
path: '/.well-known/' + recordName,
path: recordName,
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: '' })
})
Expand Down
17 changes: 16 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,23 @@ tape('Successful test against beakerbrowser.com (no well-known/dat)', function (
})
})

tape('Successful test against staltz.com (well-known redirects)', function (t) {
datDns.resolveName('staltz.com', {noDnsOverHttps: true, ignoreCache: true}, function (err, name) {
t.error(err)
t.ok(/[0-9a-f]{64}/.test(name))

datDns.resolveName('staltz.com').then(function (name2) {
t.equal(name, name2)
t.end()
}).catch(function (err) {
t.error(err)
t.end()
})
})
})

tape('List cache', function (t) {
t.is(Object.keys(datDns.listCache()).length, 6)
t.is(Object.keys(datDns.listCache()).length, 7)
t.end()
})

Expand Down