Skip to content

Commit

Permalink
Merge pull request #27 from ipfs/feat/multiaddr
Browse files Browse the repository at this point in the history
This PR introduces utility methods for multiaddr detection:

###  `isIPFS.multiaddr(input)`

Generic `multiaddr` checks if input is a valid multiaddr.
(It does not care about its type, just validity)

### `isIPFS.peerMultiaddr(input)`

`peerMultiaddr` provides easy validation of IPFS Peer addresses (direct or encapsulated).

It is a thin wrapper on top of generic `isIPFS.multiaddr(input)` check coupled with `mafmt.IPFS.matches(input)` from [mafmt](https://github.com/multiformats/js-mafmt) (low-level multiaddr validation library)

Details behind `mafmt.IPFS` can be found at https://github.com/multiformats/js-mafmt#api

**Note:** I've added only this check because `is-ipfs` is IPFS-centric utility.
If one needs more nuanced multiaddr validation, then [mafmt](https://github.com/multiformats/js-mafmt) should be used directly. 

----

Closes #26
  • Loading branch information
lidel authored Mar 3, 2019
2 parents 71eb080 + edd64f3 commit 75962d0
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 23 deletions.
51 changes: 35 additions & 16 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@

language: node_js
cache: npm
stages:
- check
- test
- cov

node_js:
- node
- '10'

os:
- linux
- osx

script: npx nyc -s npm run test:node -- --bail
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov

# Make sure we have new NPM.
before_install:
- npm install -g npm
jobs:
include:
- os: windows
cache: false

script:
- npm run lint
- npm test
- npm run coverage
- stage: check
script:
- npx aegir commitlint --travis
- npx aegir dep-check
- npm run lint

addons:
firefox: 'latest'
- stage: test
name: chrome
addons:
chrome: stable
script: npx aegir test -t browser -t webworker

before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- stage: test
name: firefox
addons:
firefox: latest
script: npx aegir test -t browser -t webworker -- --browsers FirefoxHeadless

after_success:
- npm run coverage-publish
notifications:
email: false
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j5
isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.dweb.link') // false
isIPFS.ipnsSubdomain('http://QmcNioXSC1bfJj1dcFErhUfyjFzoX2HodkRccsFFVJJvg8.ipns.dweb.link') // false
isIPFS.ipnsSubdomain('http://foo-bar.ipns.dweb.link') // false (not a PeerID)

isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234') // true
isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234/http') // true
isIPFS.multiaddr('/ip6/::1/udp/1234') // true
isIPFS.multiaddr('ip6/::1/udp/1234') // false
isIPFS.multiaddr('/yoloinvalid/::1/udp/1234') // false

isIPFS.peerMultiaddr('/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') // true
isIPFS.peerMultiaddr('/ip4/127.0.0.1/tcp/1234/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true
isIPFS.peerMultiaddr('/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true
isIPFS.peerMultiaddr('/ip4/127.0.0.1/udp/1234') // false
```

# API
Expand All @@ -107,15 +118,15 @@ Detection of IPFS Paths and identifiers in URLs is a two-stage process:
2. proper CID validation is applied to remove false-positives


## Utils
## Content Identifiers

### `isIPFS.multihash(hash)`

Returns `true` if the provided string is a valid `multihash` or `false` otherwise.

### `isIPFS.cid(hash)`

Returns `true` if the provided string is a valid `CID` or `false` otherwise.
Returns `true` if the provided string or [`CID`](https://github.com/ipld/js-cid) object represents a valid [CID](https://docs.ipfs.io/guides/concepts/cid/) or `false` otherwise.

### `isIPFS.base32cid(hash)`

Expand Down Expand Up @@ -176,6 +187,19 @@ Returns `true` if the provided string includes a valid IPFS subdomain or `false`

Returns `true` if the provided string includes a valid IPNS subdomain or `false` otherwise.

## Multiaddrs

Below methods provide basic detection of [multiaddr](https://github.com/multiformats/multiaddr)s: composable and future-proof network addresses.

Complex validation of multiaddr can be built using `isIPFS.multiaddr` and [`mafmt`](https://github.com/multiformats/js-mafmt) library.

### `isIPFS.multiaddr(addr)`

Returns `true` if the provided `string`, [`Multiaddr`](https://github.com/multiformats/js-multiaddr) or `Buffer` represents a valid multiaddr or `false` otherwise.

### `isIPFS.peerMultiaddr(addr)`

Returns `true` if the provided `string`, [`Multiaddr`](https://github.com/multiformats/js-multiaddr) or `Buffer` represents a valid "IPFS Peer" multiaddr (matching [`IPFS` format from `mafmt`](https://github.com/multiformats/js-mafmt#api)) or `false` otherwise.

# License

Expand Down
1 change: 0 additions & 1 deletion ci/Jenkinsfile

This file was deleted.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@
"author": "Francisco Dias <francisco@baiodias.com> (http://franciscodias.net/)",
"license": "MIT",
"dependencies": {
"bs58": "4.0.1",
"bs58": "^4.0.1",
"cids": "~0.5.6",
"mafmt": "^v6.0.7",
"multiaddr": "^6.0.4",
"multibase": "~0.6.0",
"multihashes": "~0.4.13"
},
"devDependencies": {
"aegir": "15.0.1",
"chai": "4.1.2",
"pre-commit": "1.2.2"
"aegir": "^18.2.0",
"chai": "^4.2.0",
"pre-commit": "^1.2.2"
},
"repository": {
"type": "git",
Expand Down
19 changes: 19 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
const base58 = require('bs58')
const multihash = require('multihashes')
const multibase = require('multibase')
const Multiaddr = require('multiaddr')
const mafmt = require('mafmt')
const CID = require('cids')

const urlPattern = /^https?:\/\/[^/]+\/(ip(f|n)s)\/((\w+).*)/
Expand Down Expand Up @@ -42,6 +44,21 @@ function isCID (hash) {
}
}

function isMultiaddr (input) {
if (!input) return false
if (Multiaddr.isMultiaddr(input)) return true
try {
new Multiaddr(input) // eslint-disable-line no-new
return true
} catch (e) {
return false
}
}

function isPeerMultiaddr (input) {
return isMultiaddr(input) && mafmt.IPFS.matches(input)
}

function isIpfs (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch = defaultHashMath) {
const formatted = convertToString(input)
if (!formatted) {
Expand Down Expand Up @@ -116,6 +133,8 @@ const ipnsSubdomain = (url) => isIpns(url, fqdnPattern, fqdnProtocolMatch, fqdnH

module.exports = {
multihash: isMultihash,
multiaddr: isMultiaddr,
peerMultiaddr: isPeerMultiaddr,
cid: isCID,
base32cid: (cid) => (isMultibase(cid) === 'base32' && isCID(cid)),
ipfsSubdomain: ipfsSubdomain,
Expand Down
8 changes: 8 additions & 0 deletions test/test-cid.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@
const base58 = require('bs58')
const expect = require('chai').expect
const isIPFS = require('../src/index')
const CID = require('cids')

describe('ipfs cid', () => {
it('isIPFS.cid should match a valid CID instance', (done) => {
const cid = new CID('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o')
const actual = isIPFS.cid(cid)
expect(actual).to.equal(true)
done()
})

it('isIPFS.cid should match a valid CIDv0 (multihash)', (done) => {
const actual = isIPFS.cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o')
expect(actual).to.equal(true)
Expand Down
143 changes: 143 additions & 0 deletions test/test-multiaddr.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/* eslint-env mocha */
'use strict'

const expect = require('chai').expect
const Multiaddr = require('multiaddr')
const isIPFS = require('../src/index')

describe('ipfs multiaddr', () => {
it('isIPFS.multiaddr should match a string with valid ip4 multiaddr', (done) => {
const actual = isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234/http')
expect(actual).to.equal(true)
done()
})

it('isIPFS.multiaddr should match a string with valid ip6 multiaddr', (done) => {
const actual = isIPFS.multiaddr('/ip6/::1/udp/1234/http')
expect(actual).to.equal(true)
done()
})

it('isIPFS.multiaddr should match a valid Multiaddr instance', (done) => {
const ma = new Multiaddr('/ip6/::1/udp/1234/http')
const actual = isIPFS.multiaddr(ma)
expect(actual).to.equal(true)
done()
})

it('isIPFS.multiaddr should match a Buffer with multiaddr', (done) => {
const ma = new Multiaddr('/ip6/::1/udp/1234/http')
const actual = isIPFS.multiaddr(Buffer.from(ma.buffer))
expect(actual).to.equal(true)
done()
})

it('isIPFS.multiaddr should not match random Buffer', (done) => {
const actual = isIPFS.multiaddr(Buffer.from('randombuffer'))
expect(actual).to.equal(false)
done()
})

it('isIPFS.multiaddr should not match an invalid multiaddr (no initial slash)', (done) => {
const actual = isIPFS.multiaddr('ip4/127.0.0.1/udp/1234/http')
expect(actual).to.equal(false)
done()
})

it('isIPFS.multiaddr should not match an invalid multiaddr (unknown namespace)', (done) => {
const actual = isIPFS.multiaddr('/yoloinvalid/127.0.0.1/udp/1234/http')
expect(actual).to.equal(false)
done()
})

it('isIPFS.multiaddr should not match an invalid multiaddr', (done) => {
const actual = isIPFS.multiaddr('noop')
expect(actual).to.equal(false)
done()
})

it('isIPFS.multiaddr should not match an invalid multiaddr data type', (done) => {
const actual = isIPFS.multiaddr(4)
expect(actual).to.equal(false)
done()
})
})

describe('ipfs peerMultiaddr', () => {
// https://github.com/multiformats/js-mafmt/blob/v6.0.6/test/index.spec.js#L137
const goodCircuit = [
'/p2p-circuit',
'/p2p-circuit/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj',
'/p2p-circuit/ip4/127.0.0.1/tcp/20008/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj',
'/p2p-circuit/ip4/1.2.3.4/tcp/3456/ws/p2p-webrtc-star/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4',
'/p2p-circuit/ip4/1.2.3.4/tcp/3456/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4',
'/p2p-circuit/ip4/127.0.0.1/tcp/4002/ipfs/QmddWMcQX6orJGHpETYMyPgXrCXCtYANMFVDCvhKoDwLqA',
'/p2p-circuit/ipfs/QmddWMcQX6orJGHpETYMyPgXrCXCtYANMFVDCvhKoDwLqA',
'/p2p-circuit/ip4/127.0.0.1/tcp/20008/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj/' +
'p2p-circuit/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj'
]
// https://github.com/multiformats/js-mafmt/blob/v6.0.6/test/index.spec.js#L157
const validPeerMultiaddrs = [
'/ip4/127.0.0.1/tcp/20008/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj',
'/ip4/1.2.3.4/tcp/3456/ws/p2p-webrtc-star/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4',
'/ip4/1.2.3.4/tcp/3456/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4',
'/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit',
'/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj'
].concat(goodCircuit)

it('isIPFS.peerMultiaddr should match a string with a valid IPFS peer', (done) => {
for (let addr of validPeerMultiaddrs) {
const actual = isIPFS.peerMultiaddr(addr)
expect(actual, `isIPFS.peerMultiaddr(${addr})`).to.equal(true)
}
done()
})

it('isIPFS.peerMultiaddr should match a valid Multiaddr instance', (done) => {
for (let addr of validPeerMultiaddrs) {
const ma = new Multiaddr(addr)
const actual = isIPFS.peerMultiaddr(ma)
expect(actual, `isIPFS.peerMultiaddr(${addr})`).to.equal(true)
}
done()
})

it('isIPFS.peerMultiaddr should match a Buffer with multiaddr', (done) => {
for (let addr of validPeerMultiaddrs) {
const ma = new Multiaddr(addr)
const actual = isIPFS.peerMultiaddr((Buffer.from(ma.buffer)))
expect(actual, `isIPFS.peerMultiaddr(${addr})`).to.equal(true)
}
done()
})

it('isIPFS.peerMultiaddr should not match random Buffer', (done) => {
const actual = isIPFS.peerMultiaddr(Buffer.from('randombuffer'))
expect(actual).to.equal(false)
done()
})

it('isIPFS.peerMultiaddr should not match an invalid multiaddr (no initial slash)', (done) => {
const actual = isIPFS.peerMultiaddr('ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4')
expect(actual).to.equal(false)
done()
})

it('isIPFS.peerMultiaddr should not match an invalid multiaddr (unknown namespace)', (done) => {
const actual = isIPFS.peerMultiaddr('/yoloinvalid/1.2.3.4/tcp/3456/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4')
expect(actual).to.equal(false)
done()
})

it('isIPFS.peerMultiaddr should not match an invalid multiaddr', (done) => {
const actual = isIPFS.peerMultiaddr('noop')
expect(actual).to.equal(false)
done()
})

it('isIPFS.peerMultiaddr should not match an invalid multiaddr data type', (done) => {
const actual = isIPFS.peerMultiaddr(4)
expect(actual).to.equal(false)
done()
})
})

0 comments on commit 75962d0

Please sign in to comment.