Skip to content

Commit

Permalink
support for fs: URIs without redirecting to http:
Browse files Browse the repository at this point in the history
fixes ipfs#48
  • Loading branch information
the8472 committed Feb 12, 2016
1 parent e694b02 commit 750c079
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 21 deletions.
6 changes: 6 additions & 0 deletions lib/gateways.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,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
85 changes: 65 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,61 @@ 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 gwUri = gw.publicUri
let httpUri = ioservice.newURI(normalized, aURI.originCharset, 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
1 change: 1 addition & 0 deletions lib/redirects.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const ipfsRequestObserver = {
observe: function (subject, topic, data) { // eslint-disable-line no-unused-vars
if (topic === 'http-on-modify-request') {
let channel = subject.QueryInterface(Ci.nsIHttpChannel)

let redirect = redirectFor(channel.URI)
if (redirect) {
channel.redirectTo(redirect)
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
79 changes: 79 additions & 0 deletions test/test-canonical-fs-uris.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'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

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', '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

0 comments on commit 750c079

Please sign in to comment.