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

feat: support subdomains in isIPFS.url(url) #32

Merged
merged 10 commits into from
Apr 5, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ node_modules

dist
lib
docs
42 changes: 34 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ $ npm install --save is-ipfs
The code published to npm that gets loaded on require is in fact an ES5 transpiled version with the right shims added. This means that you can require it and use with your favorite bundler without having to adjust asset management process.

```js
var isIPFS = require('is-ipfs')
const isIPFS = require('is-ipfs')
```


Expand Down Expand Up @@ -53,11 +53,15 @@ isIPFS.base32cid('bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va')
isIPFS.base32cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false

isIPFS.url('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
isIPFS.url('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?filename=guardian.jpg') // true
isIPFS.url('https://ipfs.io/ipns/github.com') // true
isIPFS.url('https://github.com/ipfs/js-ipfs/blob/master/README.md') // false
isIPFS.url('https://google.com') // false
isIPFS.url('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // false (use .subdomain instead)
isIPFS.url('http://en.wikipedia-on-ipfs.org.ipfs.localhost:8080') // false (use .subdomain instead)
lidel marked this conversation as resolved.
Show resolved Hide resolved

isIPFS.path('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
isIPFS.path('/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?filename=guardian.jpg') // true
Copy link
Member

Choose a reason for hiding this comment

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

Is this a thing now?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, ?filename= sets proper Content-Disposition header and all that jazz. Folks use it as more efficient way of sharing single file, as it keeps the same CID while enables filename tweaks (does not require wrapping in a directory)

I've added it to README as its important to show that URL params do not impact CID validation.

Copy link
Member

Choose a reason for hiding this comment

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

I know it does that for URLs on the gateways but I didn't know if we'd formalised it in IPFS paths.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I get your point now.

No, it is not formalized in the context of IPFS paths – ?filename=foo.jpg is just a valid filename in IPFS.

See this example:

$ ipfs object patch add-link QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn "?filename=test.jpg" QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR
QmbcBPAwCDxRMB1Qe7CRQmxdrTSkxKwM9y6rZw2FjGtbsb

$ ipfs ls /ipfs/QmbcBPAwCDxRMB1Qe7CRQmxdrTSkxKwM9y6rZw2FjGtbsb
QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR 119762 ?filename=test.jpg

When exposed on the gateway, filenames are URIencoded:
https://ipfs.io/ipfs/QmbcBPAwCDxRMB1Qe7CRQmxdrTSkxKwM9y6rZw2FjGtbsb/%3Ffilename=test.jpg

Here I just forgot / before ?, but I agree the naming overlap is confusing, so I changed it to be something else:

Suggested change
isIPFS.path('/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?filename=guardian.jpg') // true
isIPFS.path('/ipfs/QmbcBPAwCDxRMB1Qe7CRQmxdrTSkxKwM9y6rZw2FjGtbsb/?weird-filename=test.jpg') // true

isIPFS.path('/ipns/github.com') // true
isIPFS.path('/ipfs/js-ipfs/blob/master/README.md') // false

Expand Down Expand Up @@ -98,15 +102,20 @@ isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j5
isIPFS.ipnsSubdomain('http://QmcNioXSC1bfJj1dcFErhUfyjFzoX2HodkRccsFFVJJvg8.ipns.dweb.link') // false
isIPFS.ipnsSubdomain('http://foo-bar.ipns.dweb.link') // false (not a PeerID)

isIPFS.dnslinkSubdomain('http://en.wikipedia-on-ipfs.org.ipns.localhost:8080') // true
isIPFS.dnslinkSubdomain('http//bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // false
isIPFS.dnslinkSubdomain('http//hostname-without-tld.ipns.dweb.link') // false

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('/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') // true
isIPFS.peerMultiaddr('/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') // true (legacy notation)
isIPFS.peerMultiaddr('/ip4/127.0.0.1/tcp/1234/ws/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true
isIPFS.peerMultiaddr('/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true
isIPFS.peerMultiaddr('/ip4/127.0.0.1/udp/1234') // false
```

Expand All @@ -116,7 +125,7 @@ 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
2. proper CID/FQDN validation is applied to remove false-positives


## Content Identifiers
Expand Down Expand Up @@ -178,15 +187,32 @@ 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.
Returns `true` if the provided `url` string includes a valid IPFS, looks like IPNS or DNSLink subdomain or `false` otherwise.

### `isIPFS.ipfsSubdomain(url)`

Returns `true` if the provided string includes a valid IPFS subdomain or `false` otherwise.
Returns `true` if the provided `url` string includes a valid IPFS subdomain (case-insensitive CIDv1) or `false` otherwise.
Copy link
Member

Choose a reason for hiding this comment

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

Does it check for a case insensitive encoding or is this actually just base32?

Copy link
Member Author

Choose a reason for hiding this comment

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

Web browsers force-lowercase hostname in URL and is-ipfs does the same before we check if CID in subdomain is valid:
https://github.com/ipfs/is-ipfs/blob/f1823cc0b12e97ac1f202ba7c06663b5b9b5d265/src/index.js#L83-L87


### `isIPFS.ipnsSubdomain(url)`

Returns `true` if the provided string includes a valid IPNS subdomain or `false` otherwise.
Returns `true` if the provided `url` string looks like a valid IPNS subdomain
(subdomain context requires CIDv1 with `libp2p-key` multicodec) or `false`
otherwise.

**Note:** `ipnsSubdomain` method works in offline mode: it does not perform
actual IPNS record lookup over DHT or other content routing method. It may
return false-positives. To ensure IPNS record exists, make a call to
`/api/v0/name/resolve?arg=<ipnsid>`

### `isIPFS.dnslinkSubdomain(url)`

Returns `true` if the provided `url` string looks like a valid DNSLink
subdomain (such as `http://en.wikipedia-on-ipfs.org.ipns.localhost:8080`) or
`false` otherwise.

**Note:** `dnslinkSubdomain` method works in offline mode: it does not perform
actual DNSLink lookup. It may return false-positives. To ensure DNSLink exists,
make a call to `/api/v0/dns?arg=<fqdn>`

## Multiaddrs

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "is-ipfs",
"version": "0.6.3",
"description": "A set of utilities to help identify IPFS resources",
"description": "A set of utilities to help identify IPFS resources on the web",
"leadMaintainer": "Marcin Rataj <[email protected]>",
"main": "src/index.js",
"browser": {
Expand Down Expand Up @@ -34,11 +34,11 @@
"cids": "~0.7.0",
"mafmt": "^7.0.0",
"multiaddr": "^7.2.1",
"multibase": "~0.6.0",
"multibase": "~0.7.0",
"multihashes": "~0.4.13"
},
"devDependencies": {
"aegir": "^20.5.0",
"aegir": "^21.4.3",
"chai": "^4.2.0",
"pre-commit": "^1.2.2"
},
Expand Down
71 changes: 54 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ const Multiaddr = require('multiaddr')
const mafmt = require('mafmt')
const CID = require('cids')

const urlPattern = /^https?:\/\/[^/]+\/(ip(f|n)s)\/((\w+).*)/
const pathPattern = /^\/(ip(f|n)s)\/((\w+).*)/
const urlPattern = /^https?:\/\/[^/]+\/(ip[fn]s)\/([^/?#]+)/
const pathPattern = /^\/(ip[fn]s)\/([^/?#]+)/
const defaultProtocolMatch = 1
const defaultHashMath = 4
const defaultHashMath = 2

const fqdnPattern = /^https?:\/\/([^/]+)\.(ip(?:f|n)s)\.[^/]+/
const fqdnHashMatch = 1
const fqdnProtocolMatch = 2
// CID, libp2p-key or DNSLink
const subdomainPattern = /^https?:\/\/([^/]+)\.(ip[fn]s)\.[^/?]+/
const subdomainIdMatch = 1
const subdomainProtocolMatch = 2

// Fully qualified domain name (FQDN) that has an explicit .tld suffix
const fqdnWithTld = /^(([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])\.)+([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$/

function isMultihash (hash) {
const formatted = convertToString(hash)
Expand Down Expand Up @@ -76,7 +80,7 @@ function isIpfs (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch

let hash = match[hashMatch]

if (hash && pattern === fqdnPattern) {
if (hash && pattern === subdomainPattern) {
// when doing checks for subdomain context
// ensure hash is case-insensitive
// (browsers force-lowercase authority compotent anyway)
Expand All @@ -100,18 +104,49 @@ function isIpns (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch
return false
}

if (hashMatch && pattern === fqdnPattern) {
let hash = match[hashMatch]
let ipnsId = match[hashMatch]

if (ipnsId && pattern === subdomainPattern) {
// when doing checks for subdomain context
// ensure hash is case-insensitive
// (browsers force-lowercase authority compotent anyway)
hash = hash.toLowerCase()
return isCID(hash)
ipnsId = ipnsId.toLowerCase()
return isCID(ipnsId)
}

return true
}

function isDNSLink (input, pattern, protocolMatch = defaultProtocolMatch, idMatch) {
const formatted = convertToString(input)
if (!formatted) {
return false
}

const match = formatted.match(pattern)
if (!match) {
return false
}

if (match[protocolMatch] !== 'ipns') {
return false
}

const fqdn = match[idMatch]

if (fqdn && pattern === subdomainPattern) {
try {
// URL implementation in web browsers forces lowercase of the hostname
const { hostname } = new URL(`http://${fqdn}`) // eslint-disable-line no-new
// Confirm fqdn has an explicit TLD
return fqdnWithTld.test(hostname)
} catch (e) {
return false
}
}
return false
}

function isString (input) {
return typeof input === 'string'
}
Expand All @@ -128,19 +163,21 @@ function convertToString (input) {
return false
}

const ipfsSubdomain = (url) => isIpfs(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch)
const ipnsSubdomain = (url) => isIpns(url, fqdnPattern, fqdnProtocolMatch, fqdnHashMatch)
const ipfsSubdomain = (url) => isIpfs(url, subdomainPattern, subdomainProtocolMatch, subdomainIdMatch)
const ipnsSubdomain = (url) => isIpns(url, subdomainPattern, subdomainProtocolMatch, subdomainIdMatch)
const dnslinkSubdomain = (url) => isDNSLink(url, subdomainPattern, subdomainProtocolMatch, subdomainIdMatch)

module.exports = {
multihash: isMultihash,
multiaddr: isMultiaddr,
peerMultiaddr: isPeerMultiaddr,
cid: isCID,
base32cid: (cid) => (isMultibase(cid) === 'base32' && isCID(cid)),
ipfsSubdomain: ipfsSubdomain,
ipnsSubdomain: ipnsSubdomain,
subdomain: (url) => (ipfsSubdomain(url) || ipnsSubdomain(url)),
subdomainPattern: fqdnPattern,
ipfsSubdomain,
ipnsSubdomain,
dnslinkSubdomain,
subdomain: (url) => (ipfsSubdomain(url) || ipnsSubdomain(url) || dnslinkSubdomain(url)),
subdomainPattern,
ipfsUrl: (url) => isIpfs(url, urlPattern),
ipnsUrl: (url) => isIpns(url, urlPattern),
url: (url) => (isIpfs(url, urlPattern) || isIpns(url, urlPattern)),
Expand Down
10 changes: 9 additions & 1 deletion test/test-multiaddr.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,27 @@ 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/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj', // /ipfs/ is legacy notation replaced with /p2p/
'/p2p-circuit/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj',
'/p2p-circuit/ip4/127.0.0.1/tcp/20008/ws/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj',
'/p2p-circuit/ip4/127.0.0.1/tcp/20008/ws/p2p/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/ws/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4',
'/p2p-circuit/ip4/1.2.3.4/tcp/3456/p2p/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/p2p/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/127.0.0.1/tcp/20008/ws/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj',
'/ip4/127.0.0.1/tcp/20008/ws/p2p/12D3KooWFB51PRY9BxcXSH6khFXw1BZeszeLDy7C8GciskqCTZn5', // ed25519+identity multihash
'/ip4/1.2.3.4/tcp/3456/ws/p2p-webrtc-star/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4',
'/ip4/1.2.3.4/tcp/3456/ws/p2p-webrtc-star/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4',
'/ip4/1.2.3.4/tcp/3456/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4',
'/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit',
'/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit/ipfs/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj'
Expand Down
4 changes: 2 additions & 2 deletions test/test-path.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ const expect = require('chai').expect

describe('ipfs path', () => {
it('isIPFS.ipfsPath should match an ipfs path', (done) => {
const actual = isIPFS.ipfsPath('/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm')
const actual = isIPFS.ipfsPath('/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm?arg=val#hash')
expect(actual).to.equal(true)
done()
})

it('isIPFS.ipfsPath should match a complex ipfs path', (done) => {
const actual = isIPFS.ipfsPath('/ipfs/QmeWz9YZEeNFXQhHg4PnR5ZiNr5isttgi5n1tc1eD5EfGU/content/index.html')
const actual = isIPFS.ipfsPath('/ipfs/QmeWz9YZEeNFXQhHg4PnR5ZiNr5isttgi5n1tc1eD5EfGU/content/index.html?arg=val#hash')
expect(actual).to.equal(true)
done()
})
Expand Down
49 changes: 47 additions & 2 deletions test/test-subdomain.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ describe('ipfs subdomain', () => {
done()
})

it('isIPFS.ipfsSubdomain should match localhost with port', (done) => {
const actual = isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.localhost:8080')
expect(actual).to.equal(true)
done()
})

it('isIPFS.ipfsSubdomain should not match non-cid subdomains', (done) => {
const actual = isIPFS.ipfsSubdomain('http://not-a-cid.ipfs.dweb.link')
expect(actual).to.equal(false)
Expand Down Expand Up @@ -87,6 +93,38 @@ describe('ipfs subdomain', () => {
done()
})

it('isIPFS.dnslinkSubdomain should match .ipns.localhost zone with FQDN', (done) => {
// we do not support opaque strings in subdomains, only peerids
const actual = isIPFS.dnslinkSubdomain('http://docs.ipfs.io.ipns.localhost:8080/some/path')
expect(actual).to.equal(true)
done()
})

it('isIPFS.dnslinkSubdomain should match .ipns.sub.sub.domain.tld zone with FQDN', (done) => {
// we do not support opaque strings in subdomains, only peerids
const actual = isIPFS.dnslinkSubdomain('http://docs.ipfs.io.ipns.foo.bar.buzz.dweb.link')
expect(actual).to.equal(true)
done()
})

it('isIPFS.dnslinkSubdomain should match *.ipns. zone with FQDN', (done) => {
const actual = isIPFS.dnslinkSubdomain('http://docs.ipfs.io.ipns.locahost:8080')
expect(actual).to.equal(true)
done()
})

it('isIPFS.dnslinkSubdomain should not match a .ipns. zone with cidv1b32', (done) => {
const actual = isIPFS.dnslinkSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link')
expect(actual).to.equal(false)
done()
})

it('isIPFS.dnslinkSubdomain should not match if *.ipns is not a fqdn with tld', (done) => {
const actual = isIPFS.dnslinkSubdomain('http://no-fqdn-with-tld.ipns.dweb.link')
expect(actual).to.equal(false)
done()
})

it('isIPFS.subdomain should match an ipfs subdomain', (done) => {
const actual = isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link')
expect(actual).to.equal(true)
Expand All @@ -99,6 +137,13 @@ describe('ipfs subdomain', () => {
done()
})

it('isIPFS.subdomain should match .ipns.localhost zone with FQDN', (done) => {
// we do not support opaque strings in subdomains, only peerids
const actual = isIPFS.subdomain('http://docs.ipfs.io.ipns.dweb.link')
expect(actual).to.equal(true)
done()
})

it('isIPFS.subdomain should not match if fqdn does not start with cidv1b32', (done) => {
const actual = isIPFS.subdomain('http://www.bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link')
expect(actual).to.equal(false)
Expand All @@ -111,8 +156,8 @@ describe('ipfs subdomain', () => {
done()
})

it('isIPFS.subdomain should not match if ipns peerid is invalid', (done) => {
const actual = isIPFS.subdomain('http://not-a-cid.ipns.dweb.link')
it('isIPFS.subdomain should not match if *.ipns is not libp2pkey nor fqdn', (done) => {
const actual = isIPFS.subdomain('http://not-a-cid-or-dnslink.ipns.dweb.link')
expect(actual).to.equal(false)
done()
})
Expand Down
4 changes: 2 additions & 2 deletions test/test-url.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ const isIPFS = require('../src/index')

describe('ipfs url', () => {
it('isIPFS.ipfsUrl should match an ipfs url', (done) => {
const actual = isIPFS.ipfsUrl('http://ipfs.io/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm')
const actual = isIPFS.ipfsUrl('http://ipfs.io/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm?arg=val#hash')
expect(actual).to.equal(true)
done()
})

it('isIPFS.ipfsUrl should match a complex ipfs url', (done) => {
const actual = isIPFS.ipfsUrl('http://ipfs.alexandria.media/ipfs/QmeWz9YZEeNFXQhHg4PnR5ZiNr5isttgi5n1tc1eD5EfGU/content/index.html')
const actual = isIPFS.ipfsUrl('http://ipfs.alexandria.media/ipfs/QmeWz9YZEeNFXQhHg4PnR5ZiNr5isttgi5n1tc1eD5EfGU/content/index.html?arg=val#hash')
expect(actual).to.equal(true)
done()
})
Expand Down