michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: let Cc = Components.classes; michael@0: let Ci = Components.interfaces; michael@0: let Cu = Components.utils; michael@0: michael@0: this.EXPORTED_SYMBOLS = [ "ContentLinkHandler" ]; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Feeds", michael@0: "resource:///modules/Feeds.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", michael@0: "resource://gre/modules/BrowserUtils.jsm"); michael@0: michael@0: this.ContentLinkHandler = { michael@0: init: function(chromeGlobal) { michael@0: chromeGlobal.addEventListener("DOMLinkAdded", (event) => { michael@0: this.onLinkAdded(event, chromeGlobal); michael@0: }, false); michael@0: }, michael@0: michael@0: onLinkAdded: function(event, chromeGlobal) { michael@0: var link = event.originalTarget; michael@0: var rel = link.rel && link.rel.toLowerCase(); michael@0: if (!link || !link.ownerDocument || !rel || !link.href) michael@0: return; michael@0: michael@0: // Ignore sub-frames (bugs 305472, 479408). michael@0: let window = link.ownerDocument.defaultView; michael@0: if (window != window.top) michael@0: return; michael@0: michael@0: var feedAdded = false; michael@0: var iconAdded = false; michael@0: var searchAdded = false; michael@0: var rels = {}; michael@0: for (let relString of rel.split(/\s+/)) michael@0: rels[relString] = true; michael@0: michael@0: for (let relVal in rels) { michael@0: switch (relVal) { michael@0: case "feed": michael@0: case "alternate": michael@0: if (!feedAdded) { michael@0: if (!rels.feed && rels.alternate && rels.stylesheet) michael@0: break; michael@0: michael@0: if (Feeds.isValidFeed(link, link.ownerDocument.nodePrincipal, "feed" in rels)) { michael@0: chromeGlobal.sendAsyncMessage("Link:AddFeed", michael@0: {type: link.type, michael@0: href: link.href, michael@0: title: link.title}); michael@0: feedAdded = true; michael@0: } michael@0: } michael@0: break; michael@0: case "icon": michael@0: if (!iconAdded) { michael@0: if (!Services.prefs.getBoolPref("browser.chrome.site_icons")) michael@0: break; michael@0: michael@0: var uri = this.getLinkIconURI(link); michael@0: if (!uri) michael@0: break; michael@0: michael@0: [iconAdded] = chromeGlobal.sendSyncMessage("Link:AddIcon", {url: uri.spec}); michael@0: } michael@0: break; michael@0: case "search": michael@0: if (!searchAdded) { michael@0: var type = link.type && link.type.toLowerCase(); michael@0: type = type.replace(/^\s+|\s*(?:;.*)?$/g, ""); michael@0: michael@0: let re = /^(?:https?|ftp):/i; michael@0: if (type == "application/opensearchdescription+xml" && link.title && michael@0: re.test(link.href)) michael@0: { michael@0: let engine = { title: link.title, href: link.href }; michael@0: chromeGlobal.sendAsyncMessage("Link:AddSearch", michael@0: {engine: engine, michael@0: url: link.ownerDocument.documentURI}); michael@0: searchAdded = true; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: getLinkIconURI: function(aLink) { michael@0: let targetDoc = aLink.ownerDocument; michael@0: var uri = BrowserUtils.makeURI(aLink.href, targetDoc.characterSet); michael@0: michael@0: // Verify that the load of this icon is legal. michael@0: // Some error or special pages can load their favicon. michael@0: // To be on the safe side, only allow chrome:// favicons. michael@0: var isAllowedPage = [ michael@0: /^about:neterror\?/, michael@0: /^about:blocked\?/, michael@0: /^about:certerror\?/, michael@0: /^about:home$/, michael@0: ].some(function (re) re.test(targetDoc.documentURI)); michael@0: michael@0: if (!isAllowedPage || !uri.schemeIs("chrome")) { michael@0: var ssm = Services.scriptSecurityManager; michael@0: try { michael@0: ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri, michael@0: Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT); michael@0: } catch(e) { michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: try { michael@0: var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]. michael@0: getService(Ci.nsIContentPolicy); michael@0: } catch(e) { michael@0: return null; // Refuse to load if we can't do a security check. michael@0: } michael@0: michael@0: // Security says okay, now ask content policy michael@0: if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE, michael@0: uri, targetDoc.documentURIObject, michael@0: aLink, aLink.type, null) michael@0: != Ci.nsIContentPolicy.ACCEPT) michael@0: return null; michael@0: michael@0: try { michael@0: uri.userPass = ""; michael@0: } catch(e) { michael@0: // some URIs are immutable michael@0: } michael@0: return uri; michael@0: }, michael@0: };