Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 let Cc = Components.classes;
8 let Ci = Components.interfaces;
9 let Cu = Components.utils;
11 this.EXPORTED_SYMBOLS = [ "ContentLinkHandler" ];
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
14 Cu.import("resource://gre/modules/Services.jsm");
16 XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
17 "resource:///modules/Feeds.jsm");
18 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
19 "resource://gre/modules/BrowserUtils.jsm");
21 this.ContentLinkHandler = {
22 init: function(chromeGlobal) {
23 chromeGlobal.addEventListener("DOMLinkAdded", (event) => {
24 this.onLinkAdded(event, chromeGlobal);
25 }, false);
26 },
28 onLinkAdded: function(event, chromeGlobal) {
29 var link = event.originalTarget;
30 var rel = link.rel && link.rel.toLowerCase();
31 if (!link || !link.ownerDocument || !rel || !link.href)
32 return;
34 // Ignore sub-frames (bugs 305472, 479408).
35 let window = link.ownerDocument.defaultView;
36 if (window != window.top)
37 return;
39 var feedAdded = false;
40 var iconAdded = false;
41 var searchAdded = false;
42 var rels = {};
43 for (let relString of rel.split(/\s+/))
44 rels[relString] = true;
46 for (let relVal in rels) {
47 switch (relVal) {
48 case "feed":
49 case "alternate":
50 if (!feedAdded) {
51 if (!rels.feed && rels.alternate && rels.stylesheet)
52 break;
54 if (Feeds.isValidFeed(link, link.ownerDocument.nodePrincipal, "feed" in rels)) {
55 chromeGlobal.sendAsyncMessage("Link:AddFeed",
56 {type: link.type,
57 href: link.href,
58 title: link.title});
59 feedAdded = true;
60 }
61 }
62 break;
63 case "icon":
64 if (!iconAdded) {
65 if (!Services.prefs.getBoolPref("browser.chrome.site_icons"))
66 break;
68 var uri = this.getLinkIconURI(link);
69 if (!uri)
70 break;
72 [iconAdded] = chromeGlobal.sendSyncMessage("Link:AddIcon", {url: uri.spec});
73 }
74 break;
75 case "search":
76 if (!searchAdded) {
77 var type = link.type && link.type.toLowerCase();
78 type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
80 let re = /^(?:https?|ftp):/i;
81 if (type == "application/opensearchdescription+xml" && link.title &&
82 re.test(link.href))
83 {
84 let engine = { title: link.title, href: link.href };
85 chromeGlobal.sendAsyncMessage("Link:AddSearch",
86 {engine: engine,
87 url: link.ownerDocument.documentURI});
88 searchAdded = true;
89 }
90 }
91 break;
92 }
93 }
94 },
96 getLinkIconURI: function(aLink) {
97 let targetDoc = aLink.ownerDocument;
98 var uri = BrowserUtils.makeURI(aLink.href, targetDoc.characterSet);
100 // Verify that the load of this icon is legal.
101 // Some error or special pages can load their favicon.
102 // To be on the safe side, only allow chrome:// favicons.
103 var isAllowedPage = [
104 /^about:neterror\?/,
105 /^about:blocked\?/,
106 /^about:certerror\?/,
107 /^about:home$/,
108 ].some(function (re) re.test(targetDoc.documentURI));
110 if (!isAllowedPage || !uri.schemeIs("chrome")) {
111 var ssm = Services.scriptSecurityManager;
112 try {
113 ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
114 Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
115 } catch(e) {
116 return null;
117 }
118 }
120 try {
121 var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"].
122 getService(Ci.nsIContentPolicy);
123 } catch(e) {
124 return null; // Refuse to load if we can't do a security check.
125 }
127 // Security says okay, now ask content policy
128 if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
129 uri, targetDoc.documentURIObject,
130 aLink, aLink.type, null)
131 != Ci.nsIContentPolicy.ACCEPT)
132 return null;
134 try {
135 uri.userPass = "";
136 } catch(e) {
137 // some URIs are immutable
138 }
139 return uri;
140 },
141 };