From 2d26d1dd3d3a7f72c37b662554c7c47ad7fb4cbe Mon Sep 17 00:00:00 2001 From: gorhill Date: Wed, 20 Apr 2016 13:57:31 -0400 Subject: [PATCH] this fixes #1497 --- platform/chromium/vapi-background.js | 27 +++++ platform/chromium/vapi-client.js | 141 +++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 89502500e..00f5afe2b 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -903,8 +903,35 @@ vAPI.net.registerListeners = function() { } }; + // https://bugs.chromium.org/p/chromium/issues/detail?id=129353 + // https://github.com/gorhill/uBlock/issues/1497 + // Expose websocket-based network requests to uBO's filtering engine, + // logger, etc. + // Counterpart of following block of code is found in "vapi-client.js" -- + // search for "https://github.com/gorhill/uBlock/issues/1497". + var onBeforeWebsocketRequest = function(details) { + details.type = 'websocket'; + var matches = /url=([^&]+)/.exec(details.url); + details.url = decodeURIComponent(matches[1]); + var r = onBeforeRequestClient(details); + // Blocked? + if ( r && r.cancel ) { + return { cancel: true }; + } + // Returning a 1x1 transparent pixel means "not blocked". + return { redirectUrl: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' }; + }; + var onBeforeRequestClient = this.onBeforeRequest.callback; var onBeforeRequest = function(details) { + // https://github.com/gorhill/uBlock/issues/1497 + if ( + details.type === 'image' && + details.url.endsWith('ubofix=f41665f3028c7fd10eecf573336216d3') + ) { + return onBeforeWebsocketRequest(details); + } + normalizeRequestDetails(details); return onBeforeRequestClient(details); }; diff --git a/platform/chromium/vapi-client.js b/platform/chromium/vapi-client.js index 835e9cbba..b9938205d 100644 --- a/platform/chromium/vapi-client.js +++ b/platform/chromium/vapi-client.js @@ -338,6 +338,147 @@ vAPI.shutdown.add(function() { // https://www.youtube.com/watch?v=rT5zCHn0tsg // https://www.youtube.com/watch?v=E-jS4e3zacI +/******************************************************************************/ + +// https://bugs.chromium.org/p/chromium/issues/detail?id=129353 +// https://github.com/gorhill/uBlock/issues/1497 +// Trap calls to WebSocket constructor, and expose websocket-based network +// requests to uBO's filtering engine, logger, etc. +// Counterpart of following block of code is found in "vapi-background.js" -- +// search for "https://github.com/gorhill/uBlock/issues/1497". + +(function() { + // Only for http/https documents. + if ( /^https?:/.test(window.location.protocol) !== true ) { + return; + } + + var doc = document; + var parent = doc.head || doc.documentElement; + if ( parent === null ) { + return; + } + + var WebSocketWrapper = function() { + var WS = window.WebSocket; + + var onClose = function(ev) { + this.readyState = this.wrapped.readyState; + if ( this.onclose !== null ) { + this.onclose(ev); + } + }; + + var onError = function(ev) { + this.readyState = this.wrapped.readyState; + if ( this.onerror !== null ) { + this.onerror(ev); + } + }; + + var onMessage = function(ev) { + if ( this.onmessage !== null ) { + this.onmessage(ev); + } + }; + + var onOpen = function(ev) { + this.readyState = this.wrapped.readyState; + if ( this.onopen !== null ) { + this.onopen(ev); + } + }; + + var onAllowed = function(ws, url, protocols) { + this.removeEventListener('load', onAllowed); + this.removeEventListener('error', onBlocked); + connect(ws, url, protocols); + }; + + var onBlocked = function(ws) { + this.removeEventListener('load', onAllowed); + this.removeEventListener('error', onBlocked); + if ( ws.onerror !== null ) { + ws.onerror(new window.ErrorEvent('error')); + } + }; + + var connect = function(ws, url, protocols) { + ws.wrapped = new WS(url, protocols); + ws.wrapped.onclose = onClose.bind(ws); + ws.wrapped.onerror = onError.bind(ws); + ws.wrapped.onmessage = onMessage.bind(ws); + ws.wrapped.onopen = onOpen.bind(ws); + }; + + // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket + var wrapper = function(url, protocols) { + this.binaryType = ''; + this.bufferedAmount = 0; + this.extensions = ''; + this.onclose = null; + this.onerror = null; + this.onmessage = null; + this.onopen = null; + this.protocol = ''; + this.readyState = this.CONNECTING; + this.url = url; + + this.wrapped = null; + if ( /^wss?:\/\//.test(url) === false ) { + connect(this, url, protocols); + return; + } + + // We will use an image network request to communicate with uBO's + // main process. + var img = new Image(); + img.src = [ + window.location.origin, + '?url=' + encodeURIComponent(url), + '&ubofix=f41665f3028c7fd10eecf573336216d3' + ].join(''); + img.addEventListener('load', onAllowed.bind(img, this, url, protocols)); + img.addEventListener('error', onBlocked.bind(img, this, url, protocols)); + }; + + wrapper.prototype.close = function(code, reason) { + if ( this.wrapped !== null ) { + this.wrapped.close(code, reason); + } + }; + + wrapper.prototype.send = function(data) { + if ( this.wrapped !== null ) { + this.wrapped.send(data); + } + }; + + wrapper.prototype.CONNECTING = 0; + wrapper.prototype.OPEN = 1; + wrapper.prototype.CLOSING = 2; + wrapper.prototype.CLOSED = 3; + + window.WebSocket = wrapper; + + // Remove our own tag to avoid polluting document. + var me = document.getElementById('ubofix-f41665f3028c7fd10eecf573336216d3'); + if ( me !== null && me.parentNode !== null ) { + me.parentNode.removeChild(me); + } + }; + + // The script tag will remove itself from the DOM once it completes + // execution. + var script = doc.createElement('script'); + script.id = 'ubofix-f41665f3028c7fd10eecf573336216d3'; + script.textContent = '(' + WebSocketWrapper.toString() + ')();'; + try { + parent.appendChild(script); + } catch (ex) { + } +})(); + /******************************************************************************/ /******************************************************************************/