-
Notifications
You must be signed in to change notification settings - Fork 23
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/add iana ipfs #95
base: main
Are you sure you want to change the base?
Changes from all commits
5d6d1a2
a5d55d9
64332de
fb890b5
e3c7f1f
a3168eb
7f59924
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 |
---|---|---|
|
@@ -46,15 +46,19 @@ isIPFS.path('/ipfs/js-ipfs/blob/master/README.md') // false | |
isIPFS.urlOrPath('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true | ||
isIPFS.urlOrPath('https://ipfs.io/ipns/github.com') // true | ||
isIPFS.urlOrPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true | ||
isIPFS.urlOrPath('ipfs://QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true | ||
isIPFS.urlOrPath('/ipns/github.com') // 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 | ||
isIPFS.ipfsUrl('https://ipfs.io/ipfs/invalid-hash') // false | ||
isIPFS.ipfsUrl('ipfs://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true | ||
|
||
isIPFS.ipnsUrl('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false | ||
isIPFS.ipnsUrl('https://ipfs.io/ipns/github.com') // true | ||
isIPFS.ipnsUrl('ipns://ipfs.io/ipns/github.com') // 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. This should return as true because ipfs.io might reference a valid dnslink, but the also we should probably show invalid here:
|
||
|
||
isIPFS.ipfsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true | ||
isIPFS.ipfsPath('/ipfs/invalid-hash') // false | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
'use strict' | ||
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. this file shouldn't be committed here. might have been extra from a manual |
||
|
||
const multihash = require('multihashes') | ||
const multibase = require('multibase') | ||
const Multiaddr = require('multiaddr') | ||
const mafmt = require('mafmt') | ||
const CID = require('cids') | ||
const { URL } = require('iso-url') | ||
const uint8ArrayToString = require('uint8arrays/to-string') | ||
|
||
const pathGatewayPattern = /^https?:\/\/[^/]+\/(ip[fn]s)\/([^/?#]+)/ | ||
const pathPattern = /^\/(ip[fn]s)\/([^/?#]+)/ | ||
const defaultProtocolMatch = 1 | ||
const defaultHashMath = 2 | ||
|
||
// CID, libp2p-key or DNSLink | ||
const subdomainGatewayPattern = /^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) | ||
try { | ||
multihash.decode(multibase.decode('z' + formatted)) | ||
return true | ||
} catch (e) { | ||
return false | ||
} | ||
} | ||
|
||
function isMultibase (hash) { | ||
try { | ||
return multibase.isEncoded(hash) | ||
} catch (e) { | ||
return false | ||
} | ||
} | ||
|
||
function isCID (hash) { | ||
try { | ||
new CID(hash) // eslint-disable-line no-new | ||
return true | ||
} catch (e) { | ||
return false | ||
} | ||
} | ||
|
||
function isMultiaddr (input) { | ||
if (!input) return false | ||
if (Multiaddr.isMultiaddr(input)) return true | ||
try { | ||
new Multiaddr(input) // eslint-disable-line no-new | ||
return true | ||
} catch (e) { | ||
return false | ||
} | ||
} | ||
|
||
function isPeerMultiaddr (input) { | ||
return isMultiaddr(input) && mafmt.IPFS.matches(input) | ||
} | ||
|
||
function isIpfs (input, pattern, protocolMatch = defaultProtocolMatch, hashMatch = defaultHashMath) { | ||
const formatted = convertToString(input) | ||
if (!formatted) { | ||
return false | ||
} | ||
|
||
const match = formatted.match(pattern) | ||
if (!match) { | ||
return false | ||
} | ||
|
||
if (match[protocolMatch] !== 'ipfs') { | ||
return false | ||
} | ||
|
||
let hash = match[hashMatch] | ||
|
||
if (hash && pattern === subdomainGatewayPattern) { | ||
// 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, protocolMatch = defaultProtocolMatch, hashMatch) { | ||
const formatted = convertToString(input) | ||
if (!formatted) { | ||
return false | ||
} | ||
const match = formatted.match(pattern) | ||
if (!match) { | ||
return false | ||
} | ||
|
||
if (match[protocolMatch] !== 'ipns') { | ||
return false | ||
} | ||
|
||
let ipnsId = match[hashMatch] | ||
|
||
if (ipnsId && pattern === subdomainGatewayPattern) { | ||
// when doing checks for subdomain context | ||
// ensure ipnsId is case-insensitive | ||
// (browsers force-lowercase authority compotent anyway) | ||
ipnsId = ipnsId.toLowerCase() | ||
// Check if it is cidv1 | ||
if (isCID(ipnsId)) return true | ||
// Check if it looks like FQDN | ||
try { | ||
if (!ipnsId.includes('.') && ipnsId.includes('-')) { | ||
// name without tld, assuming its inlined into a single DNS label | ||
// (https://github.com/ipfs/in-web-browsers/issues/169) | ||
// en-wikipedia--on--ipfs-org → en.wikipedia-on-ipfs.org | ||
ipnsId = ipnsId.replace(/--/g, '@').replace(/-/g, '.').replace(/@/g, '-') | ||
} | ||
// URL implementation in web browsers forces lowercase of the hostname | ||
const { hostname } = new URL(`http://${ipnsId}`) // eslint-disable-line no-new | ||
// Check if potential FQDN has an explicit TLD | ||
return fqdnWithTld.test(hostname) | ||
} catch (e) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
function isString (input) { | ||
return typeof input === 'string' | ||
} | ||
|
||
function convertToString (input) { | ||
if (input instanceof Uint8Array) { | ||
return uint8ArrayToString(input, 'base58btc') | ||
} | ||
|
||
if (isString(input)) { | ||
return input | ||
} | ||
|
||
return false | ||
} | ||
|
||
const ipfsSubdomain = (url) => isIpfs(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch) | ||
const ipnsSubdomain = (url) => isIpns(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch) | ||
const subdomain = (url) => ipfsSubdomain(url) || ipnsSubdomain(url) | ||
|
||
const ipfsUrl = (url) => isIpfs(url, pathGatewayPattern) || ipfsSubdomain(url) | ||
const ipnsUrl = (url) => isIpns(url, pathGatewayPattern) || ipnsSubdomain(url) | ||
const url = (url) => ipfsUrl(url) || ipnsUrl(url) || subdomain(url) | ||
|
||
const path = (path) => isIpfs(path, pathPattern) || isIpns(path, pathPattern) | ||
|
||
module.exports = { | ||
multihash: isMultihash, | ||
multiaddr: isMultiaddr, | ||
peerMultiaddr: isPeerMultiaddr, | ||
cid: isCID, | ||
base32cid: (cid) => (isMultibase(cid) === 'base32' && isCID(cid)), | ||
ipfsSubdomain, | ||
ipnsSubdomain, | ||
subdomain, | ||
subdomainGatewayPattern, | ||
ipfsUrl, | ||
ipnsUrl, | ||
url, | ||
pathGatewayPattern: pathGatewayPattern, | ||
ipfsPath: (path) => isIpfs(path, pathPattern), | ||
ipnsPath: (path) => isIpns(path, pathPattern), | ||
path, | ||
pathPattern, | ||
urlOrPath: (x) => url(x) || path(x), | ||
cidPath: path => isString(path) && !isCID(path) && isIpfs(`/ipfs/${path}`, pathPattern) | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -107,6 +107,12 @@ describe('ipfs path', () => { | |||||
done() | ||||||
}) | ||||||
|
||||||
it('isIPFS.urlOrPath should match an IANA-schema compliant ipfs url', (done) => { | ||||||
const actual = isIPFS.urlOrPath('ipfs://QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') | ||||||
expect(actual).to.equal(true) | ||||||
done() | ||||||
}) | ||||||
|
||||||
it('isIPFS.urlOrPath should match ipns url', (done) => { | ||||||
const actual = isIPFS.urlOrPath('http://ipfs.io/ipns/foo.bar.com') | ||||||
expect(actual).to.equal(true) | ||||||
|
@@ -153,7 +159,12 @@ describe('ipfs path', () => { | |||||
}) | ||||||
|
||||||
it('isIPFS.cidPath should not match an IPFS path', () => { | ||||||
const actual = isIPFS.cidPath('/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm') | ||||||
expect(actual).to.equal(false) | ||||||
expect(isIPFS.cidPath('/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm')).to.be.false() | ||||||
}) | ||||||
}) | ||||||
|
||||||
describe('ipns path', () => { | ||||||
it('isIPFS.urlOrPath should match an IANA-schema compliant ipns url', () => { | ||||||
expect(isIPFS.urlOrPath('ipns://QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm')).to.be.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.
Suggested change
ipns:// is not valid. Only keys and domains are valid. |
||||||
}) | ||||||
}) |
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 should return as invalid since it should have a CID as the "domain" when "ipfs" is the protocol