|
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/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 let Cc = Components.classes; |
|
8 let Ci = Components.interfaces; |
|
9 let Cu = Components.utils; |
|
10 |
|
11 this.EXPORTED_SYMBOLS = [ "ContentLinkHandler" ]; |
|
12 |
|
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
14 Cu.import("resource://gre/modules/Services.jsm"); |
|
15 |
|
16 XPCOMUtils.defineLazyModuleGetter(this, "Feeds", |
|
17 "resource:///modules/Feeds.jsm"); |
|
18 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", |
|
19 "resource://gre/modules/BrowserUtils.jsm"); |
|
20 |
|
21 this.ContentLinkHandler = { |
|
22 init: function(chromeGlobal) { |
|
23 chromeGlobal.addEventListener("DOMLinkAdded", (event) => { |
|
24 this.onLinkAdded(event, chromeGlobal); |
|
25 }, false); |
|
26 }, |
|
27 |
|
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; |
|
33 |
|
34 // Ignore sub-frames (bugs 305472, 479408). |
|
35 let window = link.ownerDocument.defaultView; |
|
36 if (window != window.top) |
|
37 return; |
|
38 |
|
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; |
|
45 |
|
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; |
|
53 |
|
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; |
|
67 |
|
68 var uri = this.getLinkIconURI(link); |
|
69 if (!uri) |
|
70 break; |
|
71 |
|
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, ""); |
|
79 |
|
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 }, |
|
95 |
|
96 getLinkIconURI: function(aLink) { |
|
97 let targetDoc = aLink.ownerDocument; |
|
98 var uri = BrowserUtils.makeURI(aLink.href, targetDoc.characterSet); |
|
99 |
|
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)); |
|
109 |
|
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 } |
|
119 |
|
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 } |
|
126 |
|
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; |
|
133 |
|
134 try { |
|
135 uri.userPass = ""; |
|
136 } catch(e) { |
|
137 // some URIs are immutable |
|
138 } |
|
139 return uri; |
|
140 }, |
|
141 }; |