Skip to content

Commit

Permalink
fix: url() check should include subdomain()
Browse files Browse the repository at this point in the history
When .url was created we only had path gateways.  When .subdomain was
added, we did not update .url to test for subdomain gateways, which in
the long run will confuse people and feels like a bug.

Let's fix this: .url() will now check for both subdomain and path gateways

#32 (comment)

BREAKING CHANGE: .url(url) now returns true if .subdomain(url) is true

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>
  • Loading branch information
lidel committed Mar 24, 2020
1 parent f1823cc commit c520efc
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 31 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ 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://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true
isIPFS.url('http://en.wikipedia-on-ipfs.org.ipfs.localhost:8080') // 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)

isIPFS.path('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
isIPFS.path('/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?filename=guardian.jpg') // true
Expand All @@ -69,6 +69,7 @@ isIPFS.urlOrPath('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFo
isIPFS.urlOrPath('https://ipfs.io/ipns/github.com') // true
isIPFS.urlOrPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
isIPFS.urlOrPath('/ipns/github.com') // true
isIPFS.urlOrPath('https://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true
isIPFS.urlOrPath('https://google.com') // false

isIPFS.ipfsUrl('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
Expand Down Expand Up @@ -124,9 +125,8 @@ isIPFS.peerMultiaddr('/ip4/127.0.0.1/udp/1234') // false
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/FQDN validation is applied to remove false-positives

1. `pathPattern`/`pathGatewayPattern`/`subdomainGatewayPattern` regex is applied to quickly identify potential candidates
2. proper CID validation is applied to remove false-positives

## Content Identifiers

Expand Down
37 changes: 20 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ const Multiaddr = require('multiaddr')
const mafmt = require('mafmt')
const CID = require('cids')

const urlPattern = /^https?:\/\/[^/]+\/(ip[fn]s)\/([^/?#]+)/
const pathGatewayPattern = /^https?:\/\/[^/]+\/(ip[fn]s)\/([^/?#]+)/
const pathPattern = /^\/(ip[fn]s)\/([^/?#]+)/
const defaultProtocolMatch = 1
const defaultHashMath = 2

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

Expand Down Expand Up @@ -80,7 +80,7 @@ function isIpfs (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch

let hash = match[hashMatch]

if (hash && pattern === subdomainPattern) {
if (hash && pattern === subdomainGatewayPattern) {
// when doing checks for subdomain context
// ensure hash is case-insensitive
// (browsers force-lowercase authority compotent anyway)
Expand All @@ -106,7 +106,7 @@ function isIpns (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch

let ipnsId = match[hashMatch]

if (ipnsId && pattern === subdomainPattern) {
if (ipnsId && pattern === subdomainGatewayPattern) {
// when doing checks for subdomain context
// ensure hash is case-insensitive
// (browsers force-lowercase authority compotent anyway)
Expand Down Expand Up @@ -134,7 +134,7 @@ function isDNSLink (input, pattern, protocolMatch = defaultProtocolMatch, idMatc

const fqdn = match[idMatch]

if (fqdn && pattern === subdomainPattern) {
if (fqdn && pattern === subdomainGatewayPattern) {
try {
// URL implementation in web browsers forces lowercase of the hostname
const { hostname } = new URL(`http://${fqdn}`) // eslint-disable-line no-new
Expand Down Expand Up @@ -163,9 +163,12 @@ function convertToString (input) {
return false
}

const ipfsSubdomain = (url) => isIpfs(url, subdomainPattern, subdomainProtocolMatch, subdomainIdMatch)
const ipnsSubdomain = (url) => isIpns(url, subdomainPattern, subdomainProtocolMatch, subdomainIdMatch)
const dnslinkSubdomain = (url) => isDNSLink(url, subdomainPattern, subdomainProtocolMatch, subdomainIdMatch)
const url = (url) => (isIpfs(url, pathGatewayPattern) || isIpns(url, pathGatewayPattern) || subdomain(url))
const path = (path) => (isIpfs(path, pathPattern) || isIpns(path, pathPattern))
const ipfsSubdomain = (url) => isIpfs(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch)
const ipnsSubdomain = (url) => isIpns(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch)
const dnslinkSubdomain = (url) => isDNSLink(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch)
const subdomain = (url) => (ipfsSubdomain(url) || ipnsSubdomain(url) || dnslinkSubdomain(url))

module.exports = {
multihash: isMultihash,
Expand All @@ -176,16 +179,16 @@ module.exports = {
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)),
urlPattern: urlPattern,
subdomain,
subdomainGatewayPattern,
ipfsUrl: (url) => isIpfs(url, pathGatewayPattern),
ipnsUrl: (url) => isIpns(url, pathGatewayPattern),
url,
pathGatewayPattern: pathGatewayPattern,
ipfsPath: (path) => isIpfs(path, pathPattern),
ipnsPath: (path) => isIpns(path, pathPattern),
path: (path) => (isIpfs(path, pathPattern) || isIpns(path, pathPattern)),
pathPattern: pathPattern,
urlOrPath: (x) => (isIpfs(x, urlPattern) || isIpns(x, urlPattern) || isIpfs(x, pathPattern) || isIpns(x, pathPattern)),
path,
pathPattern,
urlOrPath: (x) => (url(x) || path(x)),
cidPath: path => isString(path) && !isCID(path) && isIpfs(`/ipfs/${path}`, pathPattern)
}
34 changes: 25 additions & 9 deletions test/test-subdomain.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,23 +168,39 @@ describe('ipfs subdomain', () => {
done()
})

/* We keep subdomain logic separate from legacy urlOrPath checks, below is a fail-safe to ensure we keep that behavior */

it('isIPFS.urlOrPath should not match ipfs url with cidv1b32 subdomain', (done) => {
it('isIPFS.urlOrPath should match ipfs url with cidv1b32 subdomain', (done) => {
const actual = isIPFS.urlOrPath('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link')
expect(actual).to.equal(false)
expect(actual).to.equal(true)
done()
})

it('isIPFS.urlOrPath should not match ipns url', (done) => {
it('isIPFS.urlOrPath should match subdomain ipns', (done) => {
const actual = isIPFS.urlOrPath('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link')
expect(actual).to.equal(false)
expect(actual).to.equal(true)
done()
})

it('isIPFS.urlOrPath should not match ipns in subdomain', (done) => {
const actual = isIPFS.urlOrPath('http://a-dnslink-website.com.ipns.dweb.link')
expect(actual).to.equal(false)
it('isIPFS.urlOrPath should match potential DNSLink in subdomain', (done) => {
const actual = isIPFS.urlOrPath('http://a-dnslink-website.com.ipns.localhost:8080')
expect(actual).to.equal(true)
done()
})

it('isIPFS.url should match ipfs url with cidv1b32 subdomain', (done) => {
const actual = isIPFS.url('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link')
expect(actual).to.equal(true)
done()
})

it('isIPFS.url should match subdomain ipns', (done) => {
const actual = isIPFS.url('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link')
expect(actual).to.equal(true)
done()
})

it('isIPFS.url should match potential DNSLink in subdomain', (done) => {
const actual = isIPFS.url('http://a-dnslink-website.com.ipns.localhost:8080')
expect(actual).to.equal(true)
done()
})
})

0 comments on commit c520efc

Please sign in to comment.