Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
10 changes: 8 additions & 2 deletions src/cli/commands/dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, ...opts }) {
resolve((async () => {
const ipfs = await getIpfs()
const path = await ipfs.dns(domain)
const path = await ipfs.dns(domain, opts)
print(path)
})())
}
Expand Down
30 changes: 28 additions & 2 deletions src/core/runtime/dns-nodejs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,29 @@

const dns = require('dns')
const _ = require('lodash')
const isIPFS = require('is-ipfs')
const errcode = require('err-code')

const maxRecursiveDepth = 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 === undefined || opts.recursive.toString() === 'true'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default true for null/undefined and boolean value of argument otherwise:

Suggested change
const recursive = opts.recursive === undefined || opts.recursive.toString() === 'true'
const recursive = opts.recursive == null ? true : Boolean(opts.recursive)

Copy link
Contributor Author

@niinpatel niinpatel Apr 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boolean("false") actually returns true not false. Arguments passed via query parameters are of string type and we need to parse those too. Once easy solution I just now came up with is this:

const recursive = opts.recursive.toString() !== 'false'

If recursive is false or "false" then only we parse it as false. true when passed anything else, including null and undefined

Copy link
Contributor Author

@niinpatel niinpatel Apr 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: That wouldn't work either because error will be thrown if opts.recursive is undefined.

The original one is probably the best solution after all, with slight modifications:

const recursive = opts.recursive == null || opts.recursive.toString() !== 'false'

Copy link
Member

@alanshaw alanshaw Apr 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguments passed via query parameters are of string type and we need to parse those too

It should be passed as a boolean - this is the documented type for the value. If a string is being passed then we need to fix our HTTP endpoint to properly parse query strings to the expected types for the calls to core.

We can't guarantee the passed value is a boolean but we can ensure that the value is a boolean for the rest of our code.

Copy link
Contributor Author

@niinpatel niinpatel Apr 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Right now, I don't think query parameters are being parsed and "type-casted" before passing to the core API. For this PR, I've just now add it here:

// 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 })


let depth
if (recursive) {
depth = maxRecursiveDepth
}

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
Expand All @@ -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)
}
Expand Down
6 changes: 4 additions & 2 deletions src/http/api/resources/dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
const Boom = require('boom')

module.exports = async (request, h) => {
if (!request.query.arg) {
const { arg: domain, ...opts } = request.query

if (!domain) {
throw Boom.badRequest("Argument 'domain' is required")
}

const path = await request.server.app.ipfs.dns(request.query.arg)
const path = await request.server.app.ipfs.dns(domain, opts)
return h.response({
Path: path
})
Expand Down
21 changes: 18 additions & 3 deletions test/cli/dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
})
})

Expand Down
62 changes: 62 additions & 0 deletions test/core/dns.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint-env mocha */
'use strict'

const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)

const IPFSFactory = require('ipfsd-ctl')
const IPFS = require('../../src/core')

describe('.dns', () => {
let ipfsd, ipfs

before(function (done) {
this.timeout(20 * 1000)

const factory = IPFSFactory.create({ type: 'proc' })

factory.spawn({
exec: IPFS,
initOptions: { bits: 512 },
config: { Bootstrap: [] }
}, (err, _ipfsd) => {
expect(err).to.not.exist()
ipfsd = _ipfsd
ipfs = _ipfsd.api
done()
})
})

after((done) => {
if (ipfsd) {
ipfsd.stop(done)
} else {
done()
}
})

// skipping because there is an error in https://ipfs.io/api/v0/dns?arg=ipfs.io
// unskip once this is resolved: https://github.com/ipfs/go-ipfs/issues/6086
it.skip('should non-recursively resolve ipfs.io', () => {
return ipfs.dns('ipfs.io', { recursive: false }).then(res => {
// matches pattern /ipns/<ipnsaddress>
expect(res).to.match(/\/ipns\/.+$/)
})
})

it('should recursively resolve ipfs.io', () => {
return ipfs.dns('ipfs.io', { recursive: true }).then(res => {
// matches pattern /ipfs/<hash>
expect(res).to.match(/\/ipfs\/.+$/)
})
})

it('should resolve subdomain docs.ipfs.io', () => {
return ipfs.dns('docs.ipfs.io').then(res => {
// matches pattern /ipfs/<hash>
expect(res).to.match(/\/ipfs\/.+$/)
})
})
})