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 multiaddr checks #27

Merged
merged 6 commits into from
Mar 3, 2019
Merged
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
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 @@ -172,6 +183,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,
Copy link
Member

Choose a reason for hiding this comment

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

Not for this PR but I'm musing on naming, I would ideally like to do something like const { isMultiaddr } = require('is-ipfs') or import { isMultiaddr } from 'is-ipfs' rather than have to import the whole library.

If we eventually start using es-modules we can tree shake the stuff we're not using but as it is now we're encouraging importing the whole library since (for example) const { cid } = require('is-ipfs') would likely conflict with cid instance vars we have in code and it's not descriptive to validate a cid like if (cid(myCid)).

Copy link
Member Author

@lidel lidel Feb 25, 2019

Choose a reason for hiding this comment

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

This is a good point, let's track this in #28

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