Left: | ||
Right: |
OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | |
3 * Copyright (C) 2006-2017 eyeo GmbH | |
4 * | |
5 * Adblock Plus is free software: you can redistribute it and/or modify | |
6 * it under the terms of the GNU General Public License version 3 as | |
7 * published by the Free Software Foundation. | |
8 * | |
9 * Adblock Plus is distributed in the hope that it will be useful, | |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 * GNU General Public License for more details. | |
13 * | |
14 * You should have received a copy of the GNU General Public License | |
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | |
16 */ | |
17 | |
18 "use strict"; | |
19 | |
20 let randomEventName = "abp-request-" + Math.random().toString(36).substr(2); | |
21 | |
22 // Proxy "should we block?" messages from checkRequest inside the injected | |
23 // code to the background page and back again. | |
24 document.addEventListener(randomEventName, event => | |
25 { | |
26 let {url, requestType} = event.detail; | |
27 | |
28 ext.backgroundPage.sendMessage({ | |
29 type: "request.blockedByWrapper", | |
30 requestType, | |
31 url | |
32 }, block => | |
33 { | |
34 document.dispatchEvent(new CustomEvent( | |
35 randomEventName + "-" + requestType + "-" + url, {detail: block} | |
36 )); | |
37 }); | |
38 }); | |
39 | |
40 let injected = function(eventName) | |
41 { | |
42 /* | |
43 * Shadow root getter wrapper | |
kzar
2017/05/12 12:06:24
I improved this comment.
| |
44 * | |
45 * After creating our shadowRoot we must wrap the getter to prevent the | |
46 * website from accessing it (#4191, #4298). This is required as a | |
47 * workaround for the lack of user style support in Chrome. | |
48 * See https://bugs.chromium.org/p/chromium/issues/detail?id=632009&desc=2 | |
49 */ | |
50 if ("shadowRoot" in Element.prototype) | |
kzar
2017/05/12 12:06:24
I changed this logic, previously we returned if El
| |
51 { | |
52 let ourShadowRoot = document.documentElement.shadowRoot; | |
53 if (ourShadowRoot) | |
54 { | |
55 let desc = Object.getOwnPropertyDescriptor(Element.prototype, | |
56 "shadowRoot"); | |
57 let shadowRoot = Function.prototype.call.bind(desc.get); | |
58 | |
59 Object.defineProperty(Element.prototype, "shadowRoot", { | |
60 configurable: true, enumerable: true, get() | |
61 { | |
62 let thisShadow = shadowRoot(this); | |
63 return thisShadow == ourShadowRoot ? null : thisShadow; | |
64 } | |
65 }); | |
66 } | |
67 } | |
68 | |
69 /* | |
kzar
2017/05/12 12:06:24
I added this comment.
| |
70 * Shared request checking code, used by both the WebSocket and | |
71 * RTCPeerConnection wrappers. | |
72 */ | |
73 let RealCustomEvent = window.CustomEvent; | |
74 let addEventListener = document.addEventListener.bind(document); | |
75 let removeEventListener = document.removeEventListener.bind(document); | |
76 let dispatchEvent = document.dispatchEvent.bind(document); | |
77 | |
78 function checkRequest(requestType, url, callback) | |
79 { | |
80 let incomingEventName = eventName + "-" + requestType + "-" + url; | |
81 | |
82 function listener(event) | |
83 { | |
84 callback(event.detail); | |
85 removeEventListener(incomingEventName, listener); | |
86 } | |
87 addEventListener(incomingEventName, listener); | |
88 | |
89 dispatchEvent(new RealCustomEvent(eventName, {detail: {url, requestType}})); | |
90 } | |
91 | |
92 // Only to be called before the page's code, not hardened. | |
93 function copyProperties(src, dest, properties) | |
94 { | |
95 for (let name of properties) | |
96 { | |
97 Object.defineProperty(dest, name, | |
98 Object.getOwnPropertyDescriptor(src, name)); | |
99 } | |
100 } | |
101 | |
102 /* | |
103 * WebSocket wrapper | |
104 * | |
105 * Required before Chrome 58, since the webRequest API didn't allow us to | |
106 * intercept WebSockets. | |
107 * See https://bugs.chromium.org/p/chromium/issues/detail?id=129353 | |
108 */ | |
109 let RealWebSocket = WebSocket; | |
110 let closeWebSocket = Function.prototype.call.bind( | |
111 RealWebSocket.prototype.close | |
112 ); | |
113 | |
114 function WrappedWebSocket(url, ...args) | |
115 { | |
116 // Throw correct exceptions if the constructor is used improperly. | |
117 if (!(this instanceof WrappedWebSocket)) return RealWebSocket(); | |
118 if (arguments.length < 1) return new RealWebSocket(); | |
119 | |
120 let websocket = new RealWebSocket(url, ...args); | |
121 | |
122 checkRequest("WEBSOCKET", websocket.url, blocked => | |
123 { | |
124 if (blocked) | |
125 closeWebSocket(websocket); | |
126 }); | |
127 | |
128 return websocket; | |
129 } | |
130 WrappedWebSocket.prototype = RealWebSocket.prototype; | |
131 window.WebSocket = WrappedWebSocket.bind(); | |
132 copyProperties(RealWebSocket, WebSocket, | |
133 ["CONNECTING", "OPEN", "CLOSING", "CLOSED", "prototype"]); | |
134 RealWebSocket.prototype.constructor = WebSocket; | |
135 | |
136 /* | |
137 * RTCPeerConnection wrapper | |
138 * | |
139 * The webRequest API in Chrome does not yet allow the blocking of | |
140 * WebRTC connections. | |
141 * See https://bugs.chromium.org/p/chromium/issues/detail?id=707683 | |
142 */ | |
143 let RealRTCPeerConnection = window.RTCPeerConnection || | |
144 window.webkitRTCPeerConnection; | |
145 let closeRTCPeerConnection = Function.prototype.call.bind( | |
146 RealRTCPeerConnection.prototype.close | |
147 ); | |
148 let RealArray = Array; | |
149 let RealString = String; | |
150 let {create: createObject, defineProperty} = Object; | |
151 | |
152 function normalizeUrl(url) | |
153 { | |
154 if (typeof url != "undefined") | |
155 return RealString(url); | |
156 } | |
157 | |
158 function safeCopyArray(originalArray, transform) | |
159 { | |
160 if (originalArray == null || typeof originalArray != "object") | |
161 return originalArray; | |
162 | |
163 let safeArray = RealArray(originalArray.length); | |
164 for (let i = 0; i < safeArray.length; i++) | |
165 { | |
166 defineProperty(safeArray, i, { | |
167 configurable: false, enumerable: false, writable: false, | |
168 value: transform(originalArray[i]) | |
169 }); | |
170 } | |
171 defineProperty(safeArray, "length", { | |
172 configurable: false, enumerable: false, writable: false, | |
173 value: safeArray.length | |
174 }); | |
175 return safeArray; | |
176 } | |
177 | |
178 // It would be much easier to use the .getConfiguration method to obtain | |
179 // the normalized and safe configuration from the RTCPeerConnection | |
180 // instance. Unfortunately its not implemented as of Chrome unstable 59. | |
181 // See https://www.chromestatus.com/feature/5271355306016768 | |
182 function protectConfiguration(configuration) | |
183 { | |
184 if (configuration == null || typeof configuration != "object") | |
185 return configuration; | |
186 | |
187 let iceServers = safeCopyArray( | |
188 configuration.iceServers, | |
189 iceServer => | |
190 { | |
191 let {url, urls} = iceServer; | |
192 | |
193 // RTCPeerConnection doesn't iterate through pseudo Arrays of urls. | |
194 if (typeof urls != "undefined" && !(urls instanceof RealArray)) | |
195 urls = [urls]; | |
196 | |
197 return createObject(iceServer, { | |
198 url: { | |
199 configurable: false, enumerable: false, writable: false, | |
200 value: normalizeUrl(url) | |
201 }, | |
202 urls: { | |
203 configurable: false, enumerable: false, writable: false, | |
204 value: safeCopyArray(urls, normalizeUrl) | |
205 } | |
206 }); | |
207 } | |
208 ); | |
209 | |
210 return createObject(configuration, { | |
211 iceServers: { | |
212 configurable: false, enumerable: false, writable: false, | |
213 value: iceServers | |
214 } | |
215 }); | |
216 } | |
217 | |
218 function checkUrl(peerconnection, url) | |
219 { | |
220 checkRequest("WEBRTC", url, blocked => | |
221 { | |
222 if (blocked) | |
223 { | |
224 // Calling .close() throws if already closed. | |
225 try | |
226 { | |
227 closeRTCPeerConnection(peerconnection); | |
228 } | |
229 catch (e) {} | |
230 } | |
231 }); | |
232 } | |
233 | |
234 function checkConfiguration(peerconnection, configuration) | |
235 { | |
236 if (configuration && configuration.iceServers) | |
237 { | |
238 for (let i = 0; i < configuration.iceServers.length; i++) | |
239 { | |
240 let iceServer = configuration.iceServers[i]; | |
241 if (iceServer) | |
242 { | |
243 if (iceServer.url) | |
244 checkUrl(peerconnection, iceServer.url); | |
245 | |
246 if (iceServer.urls) | |
247 { | |
248 for (let j = 0; j < iceServer.urls.length; j++) | |
249 checkUrl(peerconnection, iceServer.urls[j]); | |
250 } | |
251 } | |
252 } | |
253 } | |
254 } | |
255 | |
256 // Chrome unstable (tested with 59) has already implemented | |
257 // setConfiguration, so we need to wrap that if it exists too. | |
258 // https://www.chromestatus.com/feature/5596193748942848 | |
259 if (RealRTCPeerConnection.prototype.setConfiguration) | |
260 { | |
261 let realSetConfiguration = Function.prototype.call.bind( | |
262 RealRTCPeerConnection.prototype.setConfiguration | |
263 ); | |
264 | |
265 RealRTCPeerConnection.prototype.setConfiguration = function(configuration) | |
266 { | |
267 configuration = protectConfiguration(configuration); | |
268 | |
269 // Call the real method first, so that validates the configuration for | |
270 // us. Also we might as well since checkRequest is asynchronous anyway. | |
271 realSetConfiguration(this, configuration); | |
272 checkConfiguration(this, configuration); | |
273 }; | |
274 } | |
275 | |
276 function WrappedRTCPeerConnection(...args) | |
277 { | |
278 if (!(this instanceof WrappedRTCPeerConnection)) | |
279 return WrappedRTCPeerConnection(); | |
280 | |
281 let configuration = protectConfiguration(args[0]); | |
282 // Since the old webkitRTCPeerConnection constructor takes an optional | |
283 // second argument we need to take care to pass that through. Necessary | |
284 // for older versions of Chrome such as 49. | |
285 let peerconnection = new RealRTCPeerConnection(configuration, args[1]); | |
286 checkConfiguration(peerconnection, configuration); | |
287 return peerconnection; | |
288 } | |
289 | |
290 WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype; | |
291 | |
292 let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind(); | |
293 copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection, | |
294 ["caller", "generateCertificate", "name", "prototype"]); | |
295 RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection; | |
296 | |
297 if ("RTCPeerConnection" in window) | |
298 window.RTCPeerConnection = boundWrappedRTCPeerConnection; | |
299 if ("webkitRTCPeerConnection" in window) | |
300 window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection; | |
301 }; | |
302 | |
303 if (document instanceof HTMLDocument) | |
304 { | |
305 let script = document.createElement("script"); | |
306 script.type = "application/javascript"; | |
307 script.async = false; | |
308 script.textContent = "(" + injected + ")('" + randomEventName + "');"; | |
309 document.documentElement.appendChild(script); | |
310 document.documentElement.removeChild(script); | |
311 } | |
OLD | NEW |