Skip to content

Commit 8b3214d

Browse files
authored
Merge pull request #20 from ipfs/feat/subdomains
Add validation of CID in Subdomains
2 parents 6316a59 + dcef734 commit 8b3214d

11 files changed

+338
-55
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ lib-cov
1616

1717
# Coverage directory used by tools like istanbul
1818
coverage
19+
.nyc_output
1920

2021
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
2122
.grunt

.travis.yml

-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11

22
language: node_js
33
node_js:
4-
- 4
5-
- 5
64
- node
75

86
# Make sure we have new NPM.

README.md

+67-15
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
is-ipfs
22
====
33

4-
[![build status](https://secure.travis-ci.org/ipfs/is-ipfs.svg)](http://travis-ci.org/ipfs/is-ipfs)
5-
[![dignified.js](https://img.shields.io/badge/follows-dignified.js-blue.svg?style=flat-square)](https://github.com/dignifiedquire/dignified.js)
4+
[![](https://img.shields.io/github/release/ipfs/is-ipfs.svg)](https://github.com/ipfs/is-ipfs/releases/latest)
5+
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs)
66

7-
A set of utilities to help identify [IPFS](https://ipfs.io/) resources.
7+
> A set of utilities to help identify [IPFS](https://ipfs.io/) resources
88
9+
## Lead Maintainer
910

10-
## Install
11+
[Marcin Rataj](https://github.com/lidel)
12+
13+
# Install
1114

1215
### In Node.js through npm
1316

@@ -34,7 +37,7 @@ Loading this module through a script tag will make the ```IsIpfs``` obj availabl
3437
<script src="https://unpkg.com/is-ipfs/dist/index.js"></script>
3538
```
3639

37-
## Usage
40+
# Usage
3841
```javascript
3942
const isIPFS = require('is-ipfs')
4043

@@ -45,6 +48,9 @@ isIPFS.cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true (CIDv0)
4548
isIPFS.cid('zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7') // true (CIDv1)
4649
isIPFS.cid('noop') // false
4750

51+
isIPFS.base32cid('bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va') // true
52+
isIPFS.base32cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false
53+
4854
isIPFS.url('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
4955
isIPFS.url('https://ipfs.io/ipns/github.com') // true
5056
isIPFS.url('https://github.com/ipfs/js-ipfs/blob/master/README.md') // false
@@ -71,9 +77,31 @@ isIPFS.ipfsPath('/ipfs/invalid-hash') // false
7177

7278
isIPFS.ipnsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false
7379
isIPFS.ipnsPath('/ipns/github.com') // true
80+
81+
isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true
82+
isIPFS.subdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true
83+
isIPFS.subdomain('http://www.bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // false
84+
isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false
85+
86+
isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true
87+
isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false
88+
89+
isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true
90+
isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.dweb.link') // false
91+
isIPFS.ipnsSubdomain('http://QmcNioXSC1bfJj1dcFErhUfyjFzoX2HodkRccsFFVJJvg8.ipns.dweb.link') // false
92+
isIPFS.ipnsSubdomain('http://foo-bar.ipns.dweb.link') // false (not a PeerID)
7493
```
7594

76-
## API
95+
# API
96+
97+
A suite of util methods that provides efficient validation.
98+
99+
Detection of IPFS Paths and identifiers in URLs is a two-stage process:
100+
1. `urlPattern`/`pathPattern`/`subdomainPattern` regex is applied to quickly identify potential candidates
101+
2. proper CID validation is applied to remove false-positives
102+
103+
104+
## Utils
77105

78106
### `isIPFS.multihash(hash)`
79107

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

84112
Returns `true` if the provided string is a valid `CID` or `false` otherwise.
85113

86-
### `isIPFS.url(url)`
114+
### `isIPFS.base32cid(hash)`
87115

88-
Returns `true` if the provided string is a valid IPFS or IPNS url or `false` otherwise.
116+
Returns `true` if the provided string is a valid `CID` in Base32 encoding or `false` otherwise.
89117

90-
### `isIPFS.path(path)`
91-
92-
Returns `true` if the provided string is a valid IPFS or IPNS path or `false` otherwise.
118+
## URLs
93119

94-
### `isIPFS.urlOrPath(path)`
120+
### `isIPFS.url(url)`
95121

96-
Returns `true` if the provided string is a valid IPFS or IPNS url or path or `false` otherwise.
122+
Returns `true` if the provided string is a valid IPFS or IPNS url or `false` otherwise.
97123

98124
### `isIPFS.ipfsUrl(url)`
99125

@@ -103,6 +129,19 @@ Returns `true` if the provided string is a valid IPFS url or `false` otherwise.
103129

104130
Returns `true` if the provided string is a valid IPNS url or `false` otherwise.
105131

132+
## Paths
133+
134+
Standalone validation of IPFS Paths: `/ip(f|n)s/<cid>/..`
135+
136+
### `isIPFS.path(path)`
137+
138+
Returns `true` if the provided string is a valid IPFS or IPNS path or `false` otherwise.
139+
140+
### `isIPFS.urlOrPath(path)`
141+
142+
Returns `true` if the provided string is a valid IPFS or IPNS url or path or `false` otherwise.
143+
144+
106145
### `isIPFS.ipfsPath(path)`
107146

108147
Returns `true` if the provided string is a valid IPFS path or `false` otherwise.
@@ -111,10 +150,23 @@ Returns `true` if the provided string is a valid IPFS path or `false` otherwise.
111150

112151
Returns `true` if the provided string is a valid IPNS path or `false` otherwise.
113152

153+
## Subdomains
154+
155+
Validated subdomain convention: `cidv1b32.ip(f|n)s.domain.tld`
156+
157+
### `isIPFS.subdomain(url)`
158+
159+
Returns `true` if the provided string includes a valid IPFS or IPNS subdomain or `false` otherwise.
160+
161+
### `isIPFS.ipfsSubdomain(url)`
162+
163+
Returns `true` if the provided string includes a valid IPFS subdomain or `false` otherwise.
164+
165+
### `isIPFS.ipnsSubdomain(url)`
114166

115-
**Note:** the regex used for these checks is also exported as `isIPFS.urlPattern`
167+
Returns `true` if the provided string includes a valid IPNS subdomain or `false` otherwise.
116168

117-
## License
118169

170+
# License
119171

120172
MIT

ci/Jenkinsfile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
javascript()

package.json

+23-21
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,56 @@
11
{
22
"name": "is-ipfs",
3-
"version": "0.3.2",
3+
"version": "0.4.0",
44
"description": "A set of utilities to help identify IPFS resources",
55
"main": "src/index.js",
66
"browser": {
77
"fs": false
88
},
99
"scripts": {
10-
"test:node": "aegir-test node",
11-
"test:browser": "aegir-test browser",
12-
"test": "aegir-test",
13-
"lint": "aegir-lint",
14-
"release": "aegir-release",
15-
"release-minor": "aegir-release --type minor",
16-
"release-major": "aegir-release --type major",
17-
"build": "aegir-build",
18-
"coverage": "aegir-coverage",
19-
"coverage-publish": "aegir-coverage publish"
10+
"test:node": "aegir test --target node",
11+
"test:browser": "aegir test --target browser",
12+
"test": "aegir test",
13+
"lint": "aegir lint",
14+
"release": "aegir release",
15+
"release-minor": "aegir release --type minor",
16+
"release-major": "aegir release --type major",
17+
"build": "aegir build",
18+
"coverage": "aegir coverage",
19+
"coverage-publish": "aegir coverage --upload"
2020
},
2121
"pre-commit": [
2222
"test",
2323
"lint"
2424
],
2525
"keywords": [
26+
"js-ipfs",
2627
"ipfs"
2728
],
2829
"author": "Francisco Dias <[email protected]> (http://franciscodias.net/)",
2930
"license": "MIT",
3031
"dependencies": {
31-
"cids": "~0.5.1",
32-
"bs58": "^4.0.1",
33-
"multihashes": "~0.4.9"
32+
"bs58": "4.0.1",
33+
"cids": "0.5.3",
34+
"multibase": "0.4.0",
35+
"multihashes": "0.4.13"
3436
},
3537
"devDependencies": {
36-
"aegir": "^11.0.2",
37-
"chai": "^4.1.2",
38-
"pre-commit": "^1.2.2"
38+
"aegir": "15.0.1",
39+
"chai": "4.1.2",
40+
"pre-commit": "1.2.2"
3941
},
4042
"repository": {
4143
"type": "git",
42-
"url": "https://github.com/xicombd/is-ipfs.git"
44+
"url": "https://github.com/ipfs/is-ipfs.git"
4345
},
4446
"bugs": {
45-
"url": "https://github.com/xicombd/is-ipfs/issues"
47+
"url": "https://github.com/ipfs/is-ipfs/issues"
4648
},
47-
"homepage": "https://github.com/xicombd/is-ipfs",
49+
"homepage": "https://github.com/ipfs/is-ipfs",
4850
"contributors": [
4951
"David Dias <[email protected]>",
5052
"Francisco Baio Dias <[email protected]>",
5153
"Marcin Rataj <[email protected]>",
5254
"nginnever <[email protected]>"
5355
]
54-
}
56+
}

src/index.js

+46-6
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,37 @@
22

33
const base58 = require('bs58')
44
const multihash = require('multihashes')
5+
const multibase = require('multibase')
56
const CID = require('cids')
67

78
const urlPattern = /^https?:\/\/[^/]+\/(ip(f|n)s)\/((\w+).*)/
89
const pathPattern = /^\/(ip(f|n)s)\/((\w+).*)/
10+
const defaultProtocolMatch = 1
11+
const defaultHashMath = 4
12+
13+
const fqdnPattern = /^https?:\/\/([^/]+)\.(ip(?:f|n)s)\.[^/]+/
14+
const fqdnHashMatch = 1
15+
const fqdnProtocolMatch = 2
916

1017
function isMultihash (hash) {
1118
const formatted = convertToString(hash)
1219
try {
13-
const buffer = new Buffer(base58.decode(formatted))
20+
const buffer = Buffer.from(base58.decode(formatted))
1421
multihash.decode(buffer)
1522
return true
1623
} catch (e) {
1724
return false
1825
}
1926
}
2027

28+
function isMultibase (hash) {
29+
try {
30+
return multibase.isEncoded(hash)
31+
} catch (e) {
32+
return false
33+
}
34+
}
35+
2136
function isCID (hash) {
2237
try {
2338
return CID.isCID(new CID(hash))
@@ -26,7 +41,7 @@ function isCID (hash) {
2641
}
2742
}
2843

29-
function isIpfs (input, pattern) {
44+
function isIpfs (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch = defaultHashMath) {
3045
const formatted = convertToString(input)
3146
if (!formatted) {
3247
return false
@@ -37,15 +52,23 @@ function isIpfs (input, pattern) {
3752
return false
3853
}
3954

40-
if (match[1] !== 'ipfs') {
55+
if (match[protocolMatch] !== 'ipfs') {
4156
return false
4257
}
4358

44-
const hash = match[4]
59+
let hash = match[hashMatch]
60+
61+
if (hash && pattern === fqdnPattern) {
62+
// when doing checks for subdomain context
63+
// ensure hash is case-insensitive
64+
// (browsers force-lowercase authority compotent anyway)
65+
hash = hash.toLowerCase()
66+
}
67+
4568
return isCID(hash)
4669
}
4770

48-
function isIpns (input, pattern) {
71+
function isIpns (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch) {
4972
const formatted = convertToString(input)
5073
if (!formatted) {
5174
return false
@@ -55,10 +78,19 @@ function isIpns (input, pattern) {
5578
return false
5679
}
5780

58-
if (match[1] !== 'ipns') {
81+
if (match[protocolMatch] !== 'ipns') {
5982
return false
6083
}
6184

85+
if (hashMatch && pattern === fqdnPattern) {
86+
let hash = match[hashMatch]
87+
// when doing checks for subdomain context
88+
// ensure hash is case-insensitive
89+
// (browsers force-lowercase authority compotent anyway)
90+
hash = hash.toLowerCase()
91+
return isCID(hash)
92+
}
93+
6294
return true
6395
}
6496

@@ -74,9 +106,17 @@ function convertToString (input) {
74106
return false
75107
}
76108

109+
const ipfsSubdomain = (url) => isIpfs(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch)
110+
const ipnsSubdomain = (url) => isIpns(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch)
111+
77112
module.exports = {
78113
multihash: isMultihash,
79114
cid: isCID,
115+
base32cid: (cid) => (isMultibase(cid) === 'base32' && isCID(cid)),
116+
ipfsSubdomain: ipfsSubdomain,
117+
ipnsSubdomain: ipnsSubdomain,
118+
subdomain: (url) => (ipfsSubdomain(url) || ipnsSubdomain(url)),
119+
subdomainPattern: fqdnPattern,
80120
ipfsUrl: (url) => isIpfs(url, urlPattern),
81121
ipnsUrl: (url) => isIpns(url, urlPattern),
82122
url: (url) => (isIpfs(url, urlPattern) || isIpns(url, urlPattern)),

0 commit comments

Comments
 (0)