diff --git a/README.md b/README.md index 508f25d2f9..e281262e78 100644 --- a/README.md +++ b/README.md @@ -639,6 +639,7 @@ The core API is grouped into several areas: - [`ipfs.stop([callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/MISCELLANEOUS.md#stop) - `ipfs.isOnline()` - [`ipfs.resolve(name, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/MISCELLANEOUS.md#resolve) + - [`ipfs.dns(name, [options], [callback]`](https://github.com/ipfs/interface-js-ipfs-core/blob/master/SPEC/MISCELLANEOUS.md#dns) - [repo](https://github.com/ipfs/interface-ipfs-core/tree/master/SPEC/REPO.md) - `ipfs.repo.init` diff --git a/src/cli/commands/dns.js b/src/cli/commands/dns.js index 020259ef3f..f07dd0e8f6 100644 --- a/src/cli/commands/dns.js +++ b/src/cli/commands/dns.js @@ -7,15 +7,21 @@ module.exports = { describe: 'Resolve DNS links', builder: { + recursive: { + type: 'boolean', + default: true, + alias: 'r', + desc: 'Resolve until the result is not a DNS link' + }, format: { type: 'string' } }, - handler ({ getIpfs, domain, resolve }) { + handler ({ getIpfs, domain, resolve, recursive, format }) { resolve((async () => { const ipfs = await getIpfs() - const path = await ipfs.dns(domain) + const path = await ipfs.dns(domain, { recursive, format }) print(path) })()) } diff --git a/src/core/runtime/dns-nodejs.js b/src/core/runtime/dns-nodejs.js index d187ed0915..95f88d75f0 100644 --- a/src/core/runtime/dns-nodejs.js +++ b/src/core/runtime/dns-nodejs.js @@ -2,10 +2,29 @@ const dns = require('dns') const _ = require('lodash') +const isIPFS = require('is-ipfs') const errcode = require('err-code') +const MAX_RECURSIVE_DEPTH = 32 + module.exports = (domain, opts, callback) => { - resolveDnslink(domain) + // recursive is true by default, it's set to false only if explicitly passed as argument in opts + const recursive = opts.recursive == null ? true : Boolean(opts.recursive) + + let depth + if (recursive) { + depth = MAX_RECURSIVE_DEPTH + } + + return recursiveResolveDnslink(domain, depth, callback) +} + +function recursiveResolveDnslink (domain, depth, callback) { + if (depth === 0) { + return callback(errcode(`recursion limit exceeded`, 'ERR_DNSLINK_RECURSION_LIMIT')) + } + + return resolveDnslink(domain) .catch(err => { // If the code is not ENOTFOUND or ERR_DNSLINK_NOT_FOUND or ENODATA then throw the error if (err.code !== 'ENOTFOUND' && err.code !== 'ERR_DNSLINK_NOT_FOUND' && err.code !== 'ENODATA') throw err @@ -22,7 +41,14 @@ module.exports = (domain, opts, callback) => { return resolveDnslink(_dnslinkDomain) }) .then(dnslinkRecord => { - callback(null, dnslinkRecord.replace('dnslink=', '')) + const result = dnslinkRecord.replace('dnslink=', '') + const domainOrCID = result.split('/')[2] + const isIPFSCID = isIPFS.cid(domainOrCID) + + if (isIPFSCID || !depth) { + return callback(null, result) + } + return recursiveResolveDnslink(domainOrCID, depth - 1, callback) }) .catch(callback) } diff --git a/src/http/api/resources/dns.js b/src/http/api/resources/dns.js index db20a9f170..fa9ec9dea3 100644 --- a/src/http/api/resources/dns.js +++ b/src/http/api/resources/dns.js @@ -3,11 +3,19 @@ const Boom = require('boom') module.exports = async (request, h) => { - if (!request.query.arg) { + const domain = request.query.arg + + if (!domain) { throw Boom.badRequest("Argument 'domain' is required") } - const path = await request.server.app.ipfs.dns(request.query.arg) + const format = request.query.format + + // query parameters are passed as strings and need to be parsed to expected type + let recursive = request.query.recursive || request.query.r + recursive = !(recursive && recursive === 'false') + + const path = await request.server.app.ipfs.dns(domain, { recursive, format }) return h.response({ Path: path }) diff --git a/test/cli/dns.js b/test/cli/dns.js index 09286aa325..2f7a9fab10 100644 --- a/test/cli/dns.js +++ b/test/cli/dns.js @@ -3,6 +3,7 @@ const expect = require('chai').expect const runOnAndOff = require('../utils/on-and-off') +const isIPFS = require('is-ipfs') describe('dns', () => runOnAndOff((thing) => { let ipfs @@ -12,19 +13,33 @@ describe('dns', () => runOnAndOff((thing) => { ipfs = thing.ipfs }) - it('resolve ipfs.io dns', function () { + it('recursively resolve ipfs.io dns', function () { this.timeout(60 * 1000) return ipfs('dns ipfs.io').then((res) => { - expect(res.substr(0, 6)).to.eql('/ipns/') + expect(res.substr(0, 6)).to.eql('/ipfs/') + const resultingDomainOrCid = res.split('/')[2].trim() + expect(isIPFS.cid(resultingDomainOrCid)).to.eql(true) }) }) - it('resolve _dnslink.ipfs.io dns', function () { + it('recursively resolve _dnslink.ipfs.io dns', function () { this.timeout(60 * 1000) return ipfs('dns _dnslink.ipfs.io').then((res) => { + expect(res.substr(0, 6)).to.eql('/ipfs/') + const resultingDomainOrCid = res.split('/')[2].trim() + expect(isIPFS.cid(resultingDomainOrCid)).to.eql(true) + }) + }) + + it('non-recursive resolve ipfs.io', function () { + this.timeout(60 * 1000) + + return ipfs('dns --recursive false ipfs.io').then((res) => { expect(res.substr(0, 6)).to.eql('/ipns/') + const resultingDomainOrCid = res.split('/')[2].trim() + expect(isIPFS.cid(resultingDomainOrCid)).to.eql(false) }) })