Skip to content

Commit

Permalink
Replace regexes with proper IPFS path validation
Browse files Browse the repository at this point in the history
- real multihash validation
- use https://github.com/xicombd/is-ipfs
- closes #47
- use thin Browserify wrapper to run npm package
  more details:
  #47 (comment)
  • Loading branch information
lidel committed Feb 9, 2016
1 parent e713043 commit 7e98f04
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 20 deletions.
8 changes: 5 additions & 3 deletions data/linkify-demo.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<!doctype html>
<!-- open as resource://ipfs-firefox-addon-at-lidel-dot-org/data/test.html -->
<html>
<head>
<meta charset="utf-8" />

<body>
<p id='plain-links'>
Expand All @@ -10,9 +12,9 @@
<br> /ipfs/QmTAsnXoWmLZQEpvyZscrReFzqxP3pvULfGVgpJuayrp1w
<br>
</p>
<a href="fs:/ipfs/QmTAsnXoWmLZQEpvyZscrReFzqxP3pvULfGVgpJuayrp1w">abs link</a>
<a href="/ipfs/QmTAsnXoWmLZQEpvyZscrReFzqxP3pvULfGVgpJuayrp1w" id="relative-ipfs-path">relative link</a>
<a href="http://gateway.ipfs.io/ipfs/QmTAsnXoWmLZQEpvyZscrReFzqxP3pvULfGVgpJuayrp1w">gateway link</a>
<a href="fs:/ipfs/QmTAsnXoWmLZQEpvyZscrReFzqxP3pvULfGVgpJuayrp1w#1">abs link</a>
<a href="/ipfs/QmTAsnXoWmLZQEpvyZscrReFzqxP3pvULfGVgpJuayrp1w#2" id="relative-ipfs-path">relative link</a>
<a href="http://gateway.ipfs.io/ipfs/QmTAsnXoWmLZQEpvyZscrReFzqxP3pvULfGVgpJuayrp1w#3">gateway link</a>
</body>

</html>
9 changes: 2 additions & 7 deletions lib/gateways.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ const {Cc, Ci} = require('chrome')

const prefs = require('sdk/simple-prefs').prefs
const ioservice = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService)

// TODO: replace regex with 3rd party library provided by IPFS community
// Ref. https://github.com/ipfs/notes/issues/92#issuecomment-172135885
const IPFS_RESOURCE = /^https?:\/\/[^\/]+\/(ipns\/[^\/]+\.[^\/]+|ipfs\/Qm[1-9A-HJ-NP-Za-km-z]{44})/

exports.IPFS_RESOURCE = IPFS_RESOURCE
const isIPFS = require('./npm/is-ipfs.js')

var PUBLIC_GATEWAY_HOSTS // getPublicGatewayHostsRegex()
var PUBLIC_GATEWAY_URI // getDefaultPublicURI()
Expand Down Expand Up @@ -63,7 +58,7 @@ exports.onPreferencesChange = (f) => {
}

exports.isPinnable = (uriSpec) => {
return uriSpec && uriSpec.match(IPFS_RESOURCE) != null && !uriSpec.startsWith(CUSTOM_API_URI.spec)
return uriSpec && isIPFS.url(uriSpec) && !uriSpec.startsWith(CUSTOM_API_URI.spec)
}

Object.defineProperty(exports, 'customUri', {
Expand Down
312 changes: 312 additions & 0 deletions lib/npm/is-ipfs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.module || (g.module = {})).exports = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// base-x encoding
// Forked from https://github.com/cryptocoinjs/bs58
// Originally written by Mike Hearn for BitcoinJ
// Copyright (c) 2011 Google Inc
// Ported to JavaScript by Stefan Thomas
// Merged Buffer refactorings from base58-native by Stephen Pair
// Copyright (c) 2013 BitPay Inc

/**
* @param {string} ALPHABET
* @return {encode: function, decode: function}
*/
module.exports = function base (ALPHABET) {
var ALPHABET_MAP = {}
var BASE = ALPHABET.length
var LEADER = ALPHABET.charAt(0)

// pre-compute lookup table
for (var i = 0; i < ALPHABET.length; i++) {
ALPHABET_MAP[ALPHABET.charAt(i)] = i
}

/**
* @param {(Buffer|number[])} source
* @return {string}
*/
function encode (source) {
if (source.length === 0) return ''

var digits = [0]
for (var i = 0; i < source.length; ++i) {
var carry = (digits[0] << 8) + source[i]
digits[0] = carry % BASE
carry = (carry / BASE) | 0

for (var j = 1; j < digits.length; ++j) {
carry += digits[j] << 8
digits[j] = carry % BASE
carry = (carry / BASE) | 0
}

while (carry > 0) {
digits.push(carry % BASE)
carry = (carry / BASE) | 0
}
}

// deal with leading zeros
for (var k = 0; source[k] === 0 && k < source.length - 1; ++k) {
digits.push(0)
}

// convert digits to a string
for (var ii = 0, jj = digits.length - 1; ii <= jj; ++ii, --jj) {
var tmp = ALPHABET[digits[ii]]
digits[ii] = ALPHABET[digits[jj]]
digits[jj] = tmp
}

return digits.join('')
}

/**
* @param {string} string
* @return {number[]}
*/
function decode (string) {
if (string.length === 0) return []

var bytes = [0]
for (var i = 0; i < string.length; i++) {
var value = ALPHABET_MAP[string[i]]
if (value === undefined) throw new Error('Non-base' + BASE + ' character')

var carry = bytes[0] * BASE + value
bytes[0] = carry & 0xff
carry >>= 8

for (var j = 1; j < bytes.length; ++j) {
carry += bytes[j] * BASE
bytes[j] = carry & 0xff
carry >>= 8
}

while (carry > 0) {
bytes.push(carry & 0xff)
carry >>= 8
}
}

// deal with leading zeros
for (var k = 0; string[k] === LEADER && k < string.length - 1; ++k) {
bytes.push(0)
}

return bytes.reverse()
}

return {
encode: encode,
decode: decode
}
}

},{}],2:[function(require,module,exports){
var basex = require('base-x')
var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
var base58 = basex(ALPHABET)

module.exports = {
encode: base58.encode,
decode: base58.decode
}

},{"base-x":1}],3:[function(require,module,exports){
"use strict"

function invert(hash) {
var result = {}
for(var i in hash) {
if(hash.hasOwnProperty(i)) {
result[hash[i]] = i
}
}
return result
}

module.exports = invert
},{}],4:[function(require,module,exports){
(function (Buffer){
var invert = require('invert-hash')

var mh = module.exports = function () {
if (arguments.length === 1) {
return mh.decode.apply(this, arguments)
} else if (arguments.length > 1) {
return mh.encode.apply(this, arguments)
}

throw new Error('multihash must be called with the encode or decode parameters.')
}

// the multihash tables

mh.names = {
'sha1': 0x11,
'sha2-256': 0x12,
'sha2-512': 0x13,
'sha3': 0x14,
'blake2b': 0x40,
'blake2s': 0x41
}

mh.codes = invert(mh.names)

mh.defaultLengths = {
0x11: 20,
0x12: 32,
0x13: 64,
0x14: 64,
0x40: 64,
0x41: 32
}

// encode(hashfn, [length,] digest)
mh.encode = function MultihashEncode (digest, hashfn, length) {
if (!digest || !hashfn) {
throw new Error('multihash encode requires at least two args: hashfn, digest')
}

// ensure it's a hashfunction code.
hashfn = mh.coerceCode(hashfn)

if (!(Buffer.isBuffer(digest))) {
throw new Error('digest should be a Buffer')
}

if (!length) {
length = digest.length
}

if (length && digest.length !== length) {
throw new Error('digest length should be equal to specified length.')
}

if (length > 127) {
throw new Error('multihash does not yet support digest lengths greater than 127 bytes.')
}

return Buffer.concat([new Buffer([hashfn, length]), digest])
}

// decode(mutlihash)
mh.decode = function MultihashDecode (multihash) {
var err = mh.validate(multihash)
if (err) {
throw err
}

var output = {}
output.code = multihash[0]
output.name = mh.codes[output.code]
output.length = multihash[1]
output.digest = multihash.slice(2)
return output
}

mh.validate = function validateMultihash (multihash) {
if (!(Buffer.isBuffer(multihash))) {
return new Error('multihash must be a Buffer')
}

if (multihash.length < 3) {
return new Error('multihash too short. must be > 3 bytes.')
}

if (multihash.length > 129) {
return new Error('multihash too long. must be < 129 bytes.')
}

if (!mh.isAppCode(multihash[0]) && !mh.codes[multihash[0]]) {
return new Error('multihash unknown function code: 0x' + multihash[0].toString(16))
}

if (multihash.slice(2).length !== multihash[1]) {
return new Error('multihash length inconsistent: 0x' + multihash.toString('hex'))
}

return false
}

mh.coerceCode = function coerceCode (hashfn) {
var code = hashfn
if (typeof hashfn === 'string') {
if (!mh.names[hashfn]) {
throw new Error('Unrecognized hash function named: ' + hashfn)
}
code = mh.names[hashfn]
}

if (typeof code !== 'number') {
throw new Error('Hash function code should be a number. Got: ' + code)
}

if (!mh.codes[code] && !mh.isAppCode(code)) {
throw new Error('Unrecognized function code: ' + code)
}

return code
}

mh.isAppCode = function isAppCode (code) {
return code > 0 && code < 0x10
}

}).call(this,require("buffer").Buffer)
},{"buffer":undefined,"invert-hash":3}],"is-ipfs":[function(require,module,exports){
(function (Buffer){
const base58 = require('bs58')
const multihash = require('multihashes')

const urlPattern = /^https?:\/\/[^\/]+\/(ip(f|n)s)\/((\w+).*)/

function isMultihash (hash) {
try {
const buffer = new Buffer(base58.decode(hash))
multihash.decode(buffer)
return true
} catch (e) {
return false
}
}

function isIpfsUrl (url) {
const match = url.match(urlPattern)
if (!match) {
return false
}

if (match[1] !== 'ipfs') {
return false
}

const hash = match[4]
return isMultihash(hash)
}

function isIpnsUrl (url) {
const match = url.match(urlPattern)
if (!match) {
return false
}

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

return true
}

module.exports = {
multihash: isMultihash,
ipfsUrl: isIpfsUrl,
ipnsUrl: isIpnsUrl,
url: (url) => (isIpfsUrl(url) || isIpnsUrl(url)),
urlPattern: urlPattern
}

}).call(this,require("buffer").Buffer)
},{"bs58":2,"buffer":undefined,"multihashes":4}]},{},[])("is-ipfs")
});
Loading

0 comments on commit 7e98f04

Please sign in to comment.