-
Notifications
You must be signed in to change notification settings - Fork 22
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 validation of CID in Subdomains #20
Changes from 5 commits
6cc856e
ae0f738
a793da7
db6cc3c
17f9292
8e05326
dcef734
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,6 @@ | ||
|
||
language: node_js | ||
node_js: | ||
- 4 | ||
- 5 | ||
- node | ||
|
||
# Make sure we have new NPM. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,16 @@ | ||
is-ipfs | ||
==== | ||
|
||
[![build status](https://secure.travis-ci.org/ipfs/is-ipfs.svg)](http://travis-ci.org/ipfs/is-ipfs) | ||
[![dignified.js](https://img.shields.io/badge/follows-dignified.js-blue.svg?style=flat-square)](https://github.com/dignifiedquire/dignified.js) | ||
[![](https://img.shields.io/github/release/ipfs/is-ipfs.svg)](https://github.com/ipfs/is-ipfs/releases/latest) | ||
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs) | ||
|
||
A set of utilities to help identify [IPFS](https://ipfs.io/) resources. | ||
> A set of utilities to help identify [IPFS](https://ipfs.io/) resources | ||
|
||
## Lead Maintainer | ||
|
||
## Install | ||
[Marcin Rataj](https://github.com/lidel) | ||
|
||
# Install | ||
|
||
### In Node.js through npm | ||
|
||
|
@@ -34,17 +37,25 @@ Loading this module through a script tag will make the ```IsIpfs``` obj availabl | |
<script src="https://unpkg.com/is-ipfs/dist/index.js"></script> | ||
``` | ||
|
||
## Usage | ||
# Usage | ||
```javascript | ||
const isIPFS = require('is-ipfs') | ||
|
||
isIPFS.multihash('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true | ||
isIPFS.multihash('noop') // false | ||
|
||
isIPFS.multibase('bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va') // 'base32' | ||
isIPFS.multibase('zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7') // 'base58btc' | ||
isIPFS.multibase('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false (no multibase prefix in CIDv0) | ||
isIPFS.multibase('noop') // false | ||
|
||
isIPFS.cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true (CIDv0) | ||
isIPFS.cid('zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7') // true (CIDv1) | ||
isIPFS.cid('noop') // false | ||
|
||
isIPFS.base32cid('bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va') // true | ||
isIPFS.base32cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false | ||
|
||
isIPFS.url('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true | ||
isIPFS.url('https://ipfs.io/ipns/github.com') // true | ||
isIPFS.url('https://github.com/ipfs/js-ipfs/blob/master/README.md') // false | ||
|
@@ -71,29 +82,52 @@ isIPFS.ipfsPath('/ipfs/invalid-hash') // false | |
|
||
isIPFS.ipnsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false | ||
isIPFS.ipnsPath('/ipns/github.com') // true | ||
|
||
isIPFS.subdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this true for |
||
isIPFS.subdomain('http://www.bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // false | ||
isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false | ||
|
||
isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true | ||
isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false | ||
|
||
isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true | ||
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) | ||
``` | ||
|
||
## API | ||
# API | ||
|
||
A suite of util methods provides efficient validation. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... |
||
|
||
Detection of IPFS Paths and identifiers in URLs is a two-stage process: | ||
1. `urlPattern`/`pathPattern`/`subdomainPattern` regex is applied to quickly identify potential candidates | ||
2. proper CID validation is applied to remove false-positives | ||
|
||
|
||
## Utils | ||
|
||
### `isIPFS.multihash(hash)` | ||
|
||
Returns `true` if the provided string is a valid `multihash` or `false` otherwise. | ||
|
||
### `isIPFS.multibase(cid)` | ||
|
||
Returns a string with multibase name if the provided CID has `multibase` prefix or `false` otherwise. | ||
|
||
### `isIPFS.cid(hash)` | ||
|
||
Returns `true` if the provided string is a valid `CID` or `false` otherwise. | ||
|
||
### `isIPFS.url(url)` | ||
### `isIPFS.base32cid(hash)` | ||
|
||
Returns `true` if the provided string is a valid IPFS or IPNS url or `false` otherwise. | ||
Returns `true` if the provided string is a valid `CID` in Base32 encoding or `false` otherwise. | ||
|
||
### `isIPFS.path(path)` | ||
## URLs | ||
|
||
Returns `true` if the provided string is a valid IPFS or IPNS path or `false` otherwise. | ||
|
||
### `isIPFS.urlOrPath(path)` | ||
### `isIPFS.url(url)` | ||
|
||
Returns `true` if the provided string is a valid IPFS or IPNS url or path or `false` otherwise. | ||
Returns `true` if the provided string is a valid IPFS or IPNS url or `false` otherwise. | ||
|
||
### `isIPFS.ipfsUrl(url)` | ||
|
||
|
@@ -103,6 +137,19 @@ Returns `true` if the provided string is a valid IPFS url or `false` otherwise. | |
|
||
Returns `true` if the provided string is a valid IPNS url or `false` otherwise. | ||
|
||
## Paths | ||
|
||
Standalone validation of IPFS Paths: `/ip(f|n)s/<cid>/..` | ||
|
||
### `isIPFS.path(path)` | ||
|
||
Returns `true` if the provided string is a valid IPFS or IPNS path or `false` otherwise. | ||
|
||
### `isIPFS.urlOrPath(path)` | ||
|
||
Returns `true` if the provided string is a valid IPFS or IPNS url or path or `false` otherwise. | ||
|
||
|
||
### `isIPFS.ipfsPath(path)` | ||
|
||
Returns `true` if the provided string is a valid IPFS path or `false` otherwise. | ||
|
@@ -111,10 +158,23 @@ Returns `true` if the provided string is a valid IPFS path or `false` otherwise. | |
|
||
Returns `true` if the provided string is a valid IPNS path or `false` otherwise. | ||
|
||
## Subdomains | ||
|
||
Validated subdomain convention: `cidv1b32.ip(f|n)s.domain.tld` | ||
|
||
### `isIPFS.subdomain(url)` | ||
|
||
Returns `true` if the provided string includes a valid IPFS or IPNS subdomain or `false` otherwise. | ||
|
||
### `isIPFS.ipfsSubdomain(url)` | ||
|
||
Returns `true` if the provided string includes a valid IPFS subdomain or `false` otherwise. | ||
|
||
### `isIPFS.ipnsSubdomain(url)` | ||
|
||
**Note:** the regex used for these checks is also exported as `isIPFS.urlPattern` | ||
Returns `true` if the provided string includes a valid IPNS subdomain or `false` otherwise. | ||
|
||
## License | ||
|
||
# License | ||
|
||
MIT |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
javascript() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,56 @@ | ||
{ | ||
"name": "is-ipfs", | ||
"version": "0.3.2", | ||
"version": "0.4.0", | ||
"description": "A set of utilities to help identify IPFS resources", | ||
"main": "src/index.js", | ||
"browser": { | ||
"fs": false | ||
}, | ||
"scripts": { | ||
"test:node": "aegir-test node", | ||
"test:browser": "aegir-test browser", | ||
"test": "aegir-test", | ||
"lint": "aegir-lint", | ||
"release": "aegir-release", | ||
"release-minor": "aegir-release --type minor", | ||
"release-major": "aegir-release --type major", | ||
"build": "aegir-build", | ||
"coverage": "aegir-coverage", | ||
"coverage-publish": "aegir-coverage publish" | ||
"test:node": "aegir test --target node", | ||
"test:browser": "aegir test --target browser", | ||
"test": "aegir test", | ||
"lint": "aegir lint", | ||
"release": "aegir release", | ||
"release-minor": "aegir release --type minor", | ||
"release-major": "aegir release --type major", | ||
"build": "aegir build", | ||
"coverage": "aegir coverage", | ||
"coverage-publish": "aegir coverage --upload" | ||
}, | ||
"pre-commit": [ | ||
"test", | ||
"lint" | ||
], | ||
"keywords": [ | ||
"js-ipfs", | ||
"ipfs" | ||
], | ||
"author": "Francisco Dias <francisco@baiodias.com> (http://franciscodias.net/)", | ||
"license": "MIT", | ||
"dependencies": { | ||
"cids": "~0.5.1", | ||
"bs58": "^4.0.1", | ||
"multihashes": "~0.4.9" | ||
"bs58": "4.0.1", | ||
"cids": "0.5.3", | ||
"multibase": "0.4.0", | ||
"multihashes": "0.4.13" | ||
}, | ||
"devDependencies": { | ||
"aegir": "^11.0.2", | ||
"chai": "^4.1.2", | ||
"pre-commit": "^1.2.2" | ||
"aegir": "15.0.1", | ||
"chai": "4.1.2", | ||
"pre-commit": "1.2.2" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/xicombd/is-ipfs.git" | ||
"url": "https://github.com/ipfs/is-ipfs.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/xicombd/is-ipfs/issues" | ||
"url": "https://github.com/ipfs/is-ipfs/issues" | ||
}, | ||
"homepage": "https://github.com/xicombd/is-ipfs", | ||
"homepage": "https://github.com/ipfs/is-ipfs", | ||
"contributors": [ | ||
"David Dias <daviddias.p@gmail.com>", | ||
"Francisco Baio Dias <xicombd@gmail.com>", | ||
"Marcin Rataj <lidel@lidel.org>", | ||
"nginnever <ginneversource@gmail.com>" | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,22 +2,37 @@ | |
|
||
const base58 = require('bs58') | ||
const multihash = require('multihashes') | ||
const multibase = require('multibase') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You still depend directly on multibase - it should be in dependencies |
||
const CID = require('cids') | ||
|
||
const urlPattern = /^https?:\/\/[^/]+\/(ip(f|n)s)\/((\w+).*)/ | ||
const pathPattern = /^\/(ip(f|n)s)\/((\w+).*)/ | ||
const defaultProtocolMatch = 1 | ||
const defaultHashMath = 4 | ||
|
||
const fqdnPattern = /^https?:\/\/([^/]+)\.(ip(?:f|n)s)\.[^/]+/ | ||
const fqdnHashMatch = 1 | ||
const fqdnProtocolMatch = 2 | ||
|
||
function isMultihash (hash) { | ||
const formatted = convertToString(hash) | ||
try { | ||
const buffer = new Buffer(base58.decode(formatted)) | ||
const buffer = Buffer.from(base58.decode(formatted)) | ||
multihash.decode(buffer) | ||
return true | ||
} catch (e) { | ||
return false | ||
} | ||
} | ||
|
||
function isMultibase (hash) { | ||
try { | ||
return multibase.isEncoded(hash) | ||
} catch (e) { | ||
return false | ||
} | ||
} | ||
|
||
function isCID (hash) { | ||
try { | ||
return CID.isCID(new CID(hash)) | ||
|
@@ -26,7 +41,7 @@ function isCID (hash) { | |
} | ||
} | ||
|
||
function isIpfs (input, pattern) { | ||
function isIpfs (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch = defaultHashMath) { | ||
const formatted = convertToString(input) | ||
if (!formatted) { | ||
return false | ||
|
@@ -37,15 +52,23 @@ function isIpfs (input, pattern) { | |
return false | ||
} | ||
|
||
if (match[1] !== 'ipfs') { | ||
if (match[protocolMatch] !== 'ipfs') { | ||
return false | ||
} | ||
|
||
const hash = match[4] | ||
let hash = match[hashMatch] | ||
|
||
if (hash && pattern === fqdnPattern) { | ||
// when doing checks for subdomain context | ||
// ensure hash is case-insensitive | ||
// (browsers force-lowercase authority compotent anyway) | ||
hash = hash.toLowerCase() | ||
} | ||
|
||
return isCID(hash) | ||
} | ||
|
||
function isIpns (input, pattern) { | ||
function isIpns (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch) { | ||
const formatted = convertToString(input) | ||
if (!formatted) { | ||
return false | ||
|
@@ -55,10 +78,19 @@ function isIpns (input, pattern) { | |
return false | ||
} | ||
|
||
if (match[1] !== 'ipns') { | ||
if (match[protocolMatch] !== 'ipns') { | ||
return false | ||
} | ||
|
||
if (hashMatch && pattern === fqdnPattern) { | ||
let hash = match[hashMatch] | ||
// when doing checks for subdomain context | ||
// ensure hash is case-insensitive | ||
// (browsers force-lowercase authority compotent anyway) | ||
hash = hash.toLowerCase() | ||
return isCID(hash) | ||
} | ||
|
||
return true | ||
} | ||
|
||
|
@@ -76,7 +108,13 @@ function convertToString (input) { | |
|
||
module.exports = { | ||
multihash: isMultihash, | ||
multibase: isMultibase, | ||
cid: isCID, | ||
base32cid: (cid) => (isMultibase(cid) === 'base32' && isCID(cid)), | ||
ipfsSubdomain: (url) => isIpfs(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch), | ||
ipnsSubdomain: (url) => isIpns(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch), | ||
subdomain: (url) => (isIpfs(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch) || isIpns(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you're calling To make things clearer: const ipfsSubdomain = (url) => isIpfs(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch)
const ipnsSubdomain = (url) => isIpns(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch)
module.exports = {
multihash: isMultihash,
cid: isCID,
base32cid: (cid) => (isMultibase(cid) === 'base32' && isCID(cid)),
ipfsSubdomain: ipfsSubdomain,
ipnsSubdomain: ipnsSubdomain,
subdomain: (url) => (ipfsSubdomain(url) || ipnsSubdomain(url)),
...
} |
||
subdomainPattern: fqdnPattern, | ||
ipfsUrl: (url) => isIpfs(url, urlPattern), | ||
ipnsUrl: (url) => isIpns(url, urlPattern), | ||
url: (url) => (isIpfs(url, urlPattern) || isIpns(url, urlPattern)), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels out of place to me. The rest of the API returns true/false. This is a simple call-through to
multibase.isEncoded
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be changed to
true
/false
or just removed? Not sure how useful it is, maybe just remove it?(I think I've added it only because
isIPFS.base32cid
introduced multibase as a dependency)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I'd remove it. I considered that
isIPFS.base32cid
uses multibase as a dependency, and was thinking of suggesting to check like:...but js-cid depends on multibase so you're not saving any bundle bytes by depending on it and using it.