Skip to content

Commit

Permalink
Merge pull request #20 from ipfs/feat/subdomains
Browse files Browse the repository at this point in the history
Add validation of CID in Subdomains
  • Loading branch information
lidel authored Jul 23, 2018
2 parents 6316a59 + dcef734 commit 8b3214d
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 55 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ lib-cov

# Coverage directory used by tools like istanbul
coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
Expand Down
2 changes: 0 additions & 2 deletions .travis.yml
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.
Expand Down
82 changes: 67 additions & 15 deletions README.md
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

Expand All @@ -34,7 +37,7 @@ 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')

Expand All @@ -45,6 +48,9 @@ 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
Expand All @@ -71,9 +77,31 @@ isIPFS.ipfsPath('/ipfs/invalid-hash') // false

isIPFS.ipnsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false
isIPFS.ipnsPath('/ipns/github.com') // true

isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true
isIPFS.subdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true
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 that provides efficient validation.

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)`

Expand All @@ -83,17 +111,15 @@ Returns `true` if the provided string is a valid `multihash` or `false` otherwis

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)`

Returns `true` if the provided string is a valid IPFS or IPNS path or `false` otherwise.
## URLs

### `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)`

Expand All @@ -103,6 +129,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.
Expand All @@ -111,10 +150,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
1 change: 1 addition & 0 deletions ci/Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
javascript()
44 changes: 23 additions & 21 deletions package.json
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>"
]
}
}
52 changes: 46 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,37 @@

const base58 = require('bs58')
const multihash = require('multihashes')
const multibase = require('multibase')
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))
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
}

Expand All @@ -74,9 +106,17 @@ function convertToString (input) {
return false
}

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)),
Expand Down
Loading

0 comments on commit 8b3214d

Please sign in to comment.