Skip to content

Commit

Permalink
Merge pull request #70 from the8472/fs-uris
Browse files Browse the repository at this point in the history
support for fs: URIs without redirecting to http:

- disabled by default
- implements #48
  • Loading branch information
lidel committed Feb 14, 2016
2 parents 72fc46c + 0b08570 commit 0640e96
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: node_js
node_js:
- "0.12"
- "4.1"
sudo: false
addons:
apt:
Expand Down
12 changes: 12 additions & 0 deletions lib/gateways.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ Object.defineProperty(exports, 'publicUri', {
}
})

Object.defineProperty(exports, 'gwUri', {
get: function () {
return this.redirectEnabled ? CUSTOM_GATEWAY_URI : PUBLIC_GATEWAY_URI
}
})

Object.defineProperty(exports, 'publicHosts', {
get: function () {
return PUBLIC_GATEWAY_HOSTS
Expand All @@ -99,3 +105,9 @@ Object.defineProperty(exports, 'linkify', {
return prefs.linkify
}
})

Object.defineProperty(exports, 'fsUris', {
get: function () {
return prefs.fsUris
}
})
1 change: 1 addition & 0 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const parent = require('sdk/remote/parent')

exports.main = function (options, callbacks) { // eslint-disable-line no-unused-vars
require('./redirects.js')
require('./request-fix.js')
require('./peer-watch.js')
protocols.register()
parent.remoteRequire('./child-main.js', module)
Expand Down
84 changes: 64 additions & 20 deletions lib/protocols.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,39 @@ const HANDLERS = {
'web+ipns': WebIpnsProtocolHandler
}

/*
nsIURI does not not resolve relative paths properly
nsIStandardURL does, but always uses /// for authority-less URLs
thus we convert back and forth
*/
function resolveRelative (base, relative) {
let newBase = Cc['@mozilla.org/network/standard-url;1'].createInstance(Ci.nsIStandardURL)
newBase.QueryInterface(Ci.nsIURI)
newBase.init(Ci.nsIStandardURL.URLTYPE_NO_AUTHORITY, -1, base.spec, null, null)
let resolved = Cc['@mozilla.org/network/standard-url;1'].createInstance(Ci.nsIStandardURL)
resolved.init(Ci.nsIStandardURL.URLTYPE_NO_AUTHORITY, -1, relative, null, newBase)
resolved.QueryInterface(Ci.nsIURI)

// console.log("rel", base, relative, newBase, resolved)

return resolved.spec
}

CommonProtocolHandler.prototype = Object.freeze({
defaultPort: -1,
allowPort: function (port, scheme) { // eslint-disable-line no-unused-vars
return false
},

/*
since ipfs is a public network and pages could just embed their own js-ipfs code anyway to fetch the data
we allow any page to embed ipfs uris and waive CORS restrictions
TODO: consider adding URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT (discussion needed)
*/
protocolFlags: Ci.nsIProtocolHandler.URI_NOAUTH |
Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE,
Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE |
Ci.nsIProtocolHandler.URI_FETCHABLE_BY_ANYONE,

normalizedIpfsPath: function (uriSpec) {
let schemeExp = this.scheme.replace(/\+/, '\\+') // fix for web+fs etc
Expand All @@ -56,41 +81,60 @@ CommonProtocolHandler.prototype = Object.freeze({

newURI: function (aSpec, aOriginCharset, aBaseURI) {
// console.info('Detected newURI with IPFS protocol: ' + aSpec)
// console.log(aSpec, aOriginCharset, aBaseURI && aBaseURI.spec)

if (aBaseURI && aBaseURI.scheme === this.scheme) {
// presence of aBaseURI means a dependent resource or a relative link
// and we need to return correct http URI
let http = gw.publicUri.spec + this.pathPrefix + aBaseURI.path
let base = ioservice.newURI(http, null, null)
let uri = ioservice.newURI(aSpec, aOriginCharset, base)
return uri
// resolve relative within the current protocol
// leave the decision to convert to http to the next steps
aSpec = resolveRelative(aBaseURI, aSpec)
}

/* Left for future use (if we enable channel.originalURI in newChannel method)
let uri = Cc['@mozilla.org/network/simple-uri;1'].createInstance(Ci.nsIURI)
uri.spec = aSpec
return uri
*/
let normalized = this.normalizedIpfsPath(aSpec)

// console.log("norm", normalized)

if (gw.fsUris && this.scheme === FS_SCHEME && new RegExp(`^${this.scheme}:`).test(aSpec)) {
let uri = Cc['@mozilla.org/network/simple-uri;1'].createInstance(Ci.nsIURI)
uri.spec = this.scheme + ':/' + normalized
return uri
}

let http = gw.publicUri.spec + this.normalizedIpfsPath(aSpec)
let uri = ioservice.newURI(http, aOriginCharset, null)
let uri = ioservice.newURI(normalized, aOriginCharset, gw.publicUri)

// console.info('newURI routed to HTTP gateway: ' + uri.spec)
return uri
},

newChannel: function (aURI) {
newChannel2: function (aURI, loadInfo) {
// console.info('Detected newChannel for IPFS protocol: ' + aURI.spec)
let http = gw.publicUri.spec + this.pathPrefix + aURI.path
let channel = ioservice.newChannel(http, aURI.originCharset, null)
let normalized = this.normalizedIpfsPath(this.pathPrefix + aURI.path)
let httpUri = ioservice.newURI(normalized, aURI.originCharset, gw.gwUri)
let channel = null

if (loadInfo !== null) {
channel = ioservice.newChannelFromURIWithLoadInfo(httpUri, loadInfo)
} else {
channel = ioservice.newChannelFromURI(httpUri)
}

// line below would keep nice protocol URL in GUI
// but is disabled for now due to issues like
// https://github.com/lidel/ipfs-firefox-addon/issues/3
// channel.originalURI = aURI
if (gw.fsUris && this.scheme === FS_SCHEME) {
channel.originalURI = aURI
channel.loadFlags &= ~Ci.nsIChannel.LOAD_REPLACE
// channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS
}

channel.loadInfo = loadInfo

channel.QueryInterface(Ci.nsIWritablePropertyBag)
channel.QueryInterface(Ci.nsIWritablePropertyBag2)
channel.setPropertyAsAString('ipfs-uri', aURI.spec)

// console.info('newChannel routed to HTTP gateway: ' + channel.URI.spec)
return channel
},
newChannel: function (aUri) {
return this.newChannel2(aUri, null)
}

})
Expand Down
49 changes: 49 additions & 0 deletions lib/request-fix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict'

const {Ci} = require('chrome')
const events = require('sdk/system/events')
const gw = require('./gateways.js')

/*
const catman = Cc['@mozilla.org/categorymanager;1'].getService(CI.nsICategoryManager)
// category ("net-channel-event-sinks")
catman.addCategoryEntry(in string aCategory, in string aEntry, in string aValue, in boolean aPersist, in boolean aReplace)
*/

function fixupRequest (e) {
if (e.type !== 'http-on-modify-request') {
return
}
if (!gw.fsUris) {
return
}
const channel = e.subject.QueryInterface(Ci.nsIHttpChannel)

channel.QueryInterface(Ci.nsIHttpChannelInternal)
channel.QueryInterface(Ci.nsIPropertyBag2)
channel.QueryInterface(Ci.nsIPropertyBag)

let isIpfsReq = null
try {
isIpfsReq = channel.hasKey('ipfs-uri')
} catch (e) {
// console.log(e)
}

if (isIpfsReq && channel.originalURI.scheme === 'fs') {
/*
// TODO: investigate effects of the following flags
// cookies make no sense in the ipfs context, we don't want to carry different gateway cookies into the page
channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS
// should only do that for /ipfs/ paths since those are stable
channel.loadFlags |= Ci.nsIRequest.VALIDATE_NEVER
*/

// prevent redirects from replacing the effective URI
channel.loadFlags &= ~Ci.nsIChannel.LOAD_REPLACE

}

}

events.on('http-on-modify-request', fixupRequest)
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
"title": "Extended IPFS Link Support",
"type": "bool",
"value": false
},
{
"name": "fsUris",
"title": "[Experimental] display fs:/ URIs as-is instead of rewriting to http://",
"type": "bool",
"value": false
}
],
"permissions": {
Expand Down
25 changes: 9 additions & 16 deletions test/prefs-util.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
'use strict'

const { before, after } = require('sdk/test/utils')
const { before } = require('sdk/test/utils')
const { prefs } = require('sdk/simple-prefs')

exports.storePrefs = (backup) => {
// console.log('Backing up simple-prefs')
if (!backup) {
backup = new Map()
}
for (let key in prefs) {
backup.set(key, prefs[key])
}
return backup
const backup = new Map()

for (let key in prefs) {
backup.set(key, prefs[key])
}

exports.restorePrefs = (backup) => {
function restorePrefs () {
// console.log('Restoring simple-prefs')
for (let [key, data] of backup) {
prefs[key] = data
}
}

exports.restorePrefs = restorePrefs

exports.isolateTestCases = (testCases) => {
let backup = null
before(testCases, (name, assert) => {
backup = exports.storePrefs()
})
after(testCases, (name, assert) => {
exports.restorePrefs(backup)
restorePrefs()
})
}
86 changes: 86 additions & 0 deletions test/test-canonical-fs-uris.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict'

const tabs = require('sdk/tabs')
const protocols = require('../lib/protocols.js')
const fsFactory = protocols.fs

protocols.register()

const fs = fsFactory.createInstance()
const gw = require('../lib/gateways.js')
const self = require('sdk/self')
const testpage = self.data.url('linkify-demo.html')
const sripage = 'fs:/ipfs/QmSrCRJmzE4zE1nAfWPbzVfanKQNBhp7ZWmMnEdbiLvYNh/mdown#sample.md'
const parent = require('sdk/remote/parent')

const childMain = require.resolve('../lib/child-main.js')

parent.remoteRequire(childMain)

const {Cc, Ci} = require('chrome')
const ioservice = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService)

ioservice.newURI('fs:/ipns/foo', null, null)

exports['test newURI'] = function (assert) {
require('sdk/simple-prefs').prefs.fsUris = true

assert.equal(fs.newURI('fs:/ipns/foo', null, null).spec, 'fs:/ipns/foo', 'keeps fs:/ uris as-is')
}

exports['test newChannel'] = function (assert) {
require('sdk/simple-prefs').prefs.fsUris = true
gw.redirectEnabled = false

let uri = fs.newURI('fs:///ipns/foo', null, null)
let chan = fs.newChannel(uri)

assert.equal(chan.originalURI.spec, 'fs:/ipns/foo', "keeps fs: URI as channel's originalURI")

// double and triple slashes lead to gateway redirects, which cause CORS troubles -> check normalization
assert.equal(chan.URI.spec, 'https://ipfs.io/ipns/foo', 'redirect off, channel has normalized http urls')

gw.redirectEnabled = true

chan = fs.newChannel(uri)

assert.equal(chan.URI.spec, 'http://127.0.0.1:8080/ipns/foo', 'redirect on, channel has normalized http urls')
}

// https://github.com/lidel/ipfs-firefox-addon/issues/3
exports['test subresource loading'] = function (assert, done) {
require('sdk/simple-prefs').prefs.fsUris = true
gw.redirectEnabled = false

tabs.open({
url: testpage,
onReady: (tab) => {

// first load somehow doesn't have protocol handlers registered. so load resource:// first, then redirect to fs:/ page
if (tab.url !== sripage) {
tab.url = sripage
tab.reload()
return
}

let worker = tab.attach({
contentScript: `
let obs = new MutationObserver(function(mutations) {
let result = (document.querySelector("#ipfs-markdown-reader") instanceof HTMLHeadingElement)
self.port.emit("test result", {result: result})
})
obs.observe(document.body,{childList: true})
`
})
worker.port.on('test result', (msg) => {
assert.equal(msg.result, true, 'subresource loaded successfully')

require('sdk/simple-prefs').prefs.fsUris = false
tab.close(done)
})
}
})
}

require('./prefs-util.js').isolateTestCases(exports)
require('sdk/test').run(exports)
4 changes: 3 additions & 1 deletion test/test-linkify.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ const tabs = require('sdk/tabs')
const parent = require('sdk/remote/parent')
const self = require('sdk/self')
const testpage = self.data.url('linkify-demo.html')
const prefs = require('sdk/simple-prefs').prefs

require('../lib/rewrite-pages.js')
parent.remoteRequire('resource://ipfs-firefox-addon-at-lidel-dot-org/lib/rewrite-pages.js')

exports['test link processing, plain text conversion'] = function (assert, done) {
require('sdk/simple-prefs').prefs.linkify = true
prefs.linkify = true
prefs.fsUris = true

tabs.open({
url: testpage,
Expand Down
7 changes: 5 additions & 2 deletions test/test-protocols.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var proto = require('../lib/protocols.js')
var gw = require('../lib/gateways.js')
const prefs = require('sdk/simple-prefs').prefs
const pubGwUri = gw.publicUri
const ipfsPath = 'QmTkzDwWqPbnAh5YiV5VwcTLnGdwSNsNTn2aDxdXBFca7D/example#/ipfs/QmSsNVuALPa1TW1GDahup8fFDqo95iFyPE7E6HpqDivw3p/readme.md'
const ipnsPath = 'ipfs.git.sexy'
Expand Down Expand Up @@ -164,10 +165,12 @@ exports['test newURI(web+fs:///ipns/<path>)'] = function (assert) {
}

exports['test protocol rewrite'] = function (assert) {
prefs.fsUris = true

assert.equal(proto.rewrite('ipfs:QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm'),
'ipfs:QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm', '#1')
'https://ipfs.io/ipfs/QmYHNYAaYK5hm3ZhZFx5W9H6xydKDGimjdgJMrMSdnctEm', '#1')

assert.equal(proto.rewrite('ipns:ipfs.io'), 'ipns:ipfs.io', '#2')
assert.equal(proto.rewrite('ipns:ipfs.io'), 'https://ipfs.io/ipns/ipfs.io', '#2')

assert.equal(proto.rewrite('fs:/ipns/ipfs.io'), 'fs:/ipns/ipfs.io', '#3')

Expand Down

0 comments on commit 0640e96

Please sign in to comment.