From 5c15f685f185a7ecfe2e1f70d88f6a362e7f5575 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 31 Mar 2018 18:47:56 -0400 Subject: [PATCH] add workaround for Firefox's inability to redirect xhr to data: URI --- platform/webext/vapi-webrequest.js | 109 ++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 17 deletions(-) diff --git a/platform/webext/vapi-webrequest.js b/platform/webext/vapi-webrequest.js index 1b33eed74..515e01f3b 100644 --- a/platform/webext/vapi-webrequest.js +++ b/platform/webext/vapi-webrequest.js @@ -43,7 +43,7 @@ vAPI.net.registerListeners = function() { // https://github.com/gorhill/uBlock/issues/2950 // Firefox 55 does not normalize URLs to ASCII, uBO must do this itself. // https://bugzilla.mozilla.org/show_bug.cgi?id=945240 - var mustPunycode = false; + let mustPunycode = false; (function() { if ( typeof browser === 'object' && @@ -58,10 +58,10 @@ vAPI.net.registerListeners = function() { } })(); - var wrApi = browser.webRequest; + let wrApi = browser.webRequest; // legacy Chromium understands only these network request types. - var validTypes = new Set([ + let validTypes = new Set([ 'image', 'main_frame', 'object', @@ -80,14 +80,14 @@ vAPI.net.registerListeners = function() { } } - var denormalizeTypes = function(aa) { + let denormalizeTypes = function(aa) { if ( aa.length === 0 ) { return Array.from(validTypes); } - var out = new Set(), + let out = new Set(), i = aa.length; while ( i-- ) { - var type = aa[i]; + let type = aa[i]; if ( validTypes.has(type) ) { out.add(type); } @@ -98,12 +98,12 @@ vAPI.net.registerListeners = function() { return Array.from(out); }; - var punycode = self.punycode; - var reAsciiHostname = /^https?:\/\/[0-9a-z_.:@-]+[/?#]/; - var reNetworkURI = /^(?:ftps?|https?|wss?)/; - var parsedURL = new URL('about:blank'); + let punycode = self.punycode; + let reAsciiHostname = /^https?:\/\/[0-9a-z_.:@-]+[/?#]/; + let reNetworkURI = /^(?:ftps?|https?|wss?)/; + let parsedURL = new URL('about:blank'); - var normalizeRequestDetails = function(details) { + let normalizeRequestDetails = function(details) { if ( details.tabId === vAPI.noTabId && reNetworkURI.test(details.documentUrl) @@ -119,7 +119,7 @@ vAPI.net.registerListeners = function() { ); } - var type = details.type; + let type = details.type; // https://github.com/gorhill/uBlock/issues/1493 // Chromium 49+/WebExtensions support a new request type: `ping`, @@ -135,10 +135,85 @@ vAPI.net.registerListeners = function() { } }; - var onBeforeRequestClient = this.onBeforeRequest.callback; - var onBeforeRequest = function(details) { + // This is to work around Firefox's inability to redirect xmlhttprequest + // to data: URI. + let pseudoRedirector = { + filters: new Map(), + reDataURI: /^data:\w+\/\w+;base64,/, + dec: null, + init: function() { + this.dec = new Uint8Array(128); + let s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + for ( let i = 0, n = s.length; i < n; i++ ) { + this.dec[s.charCodeAt(i)] = i; + } + }, + start: function(requestId, redirectUrl) { + if ( this.dec === null ) { this.init(); } + let match = this.reDataURI.exec(redirectUrl); + if ( match === null ) { return redirectUrl; } + let s = redirectUrl.slice(match[0].length).replace(/=*$/, ''); + let f = browser.webRequest.filterResponseData(requestId); + f.onstop = this.done; + f.onerror = this.disconnect; + this.filters.set(f, s); + }, + done: function() { + let pr = pseudoRedirector; + let bufIn = pr.filters.get(this); + if ( bufIn === undefined ) { return pr.disconnect(this); } + let dec = pr.dec; + let sizeIn = bufIn.length; + let iIn = 0; + let sizeOut = sizeIn * 6 >>> 3; + let bufOut = new Uint8Array(sizeOut); + let iOut = 0; + let n = sizeIn & ~3; + while ( iIn < n ) { + let b0 = dec[bufIn.charCodeAt(iIn++)]; + let b1 = dec[bufIn.charCodeAt(iIn++)]; + let b2 = dec[bufIn.charCodeAt(iIn++)]; + let b3 = dec[bufIn.charCodeAt(iIn++)]; + bufOut[iOut++] = (b0 << 2) & 0xFC | (b1 >>> 4); + bufOut[iOut++] = (b1 << 4) & 0xF0 | (b2 >>> 2); + bufOut[iOut++] = (b2 << 6) & 0xC0 | b3; + } + if ( n < sizeIn ) { + let b0 = dec[bufIn.charCodeAt(iIn++)]; + let b1 = dec[bufIn.charCodeAt(iIn++)]; + if ( (sizeIn & 3) === 2 ) { + let b2 = dec[bufIn.charCodeAt(iIn++)]; + bufOut[iOut++] = (b0 << 2) & 0xFC | (b1 >>> 4); + bufOut[iOut++] = (b1 << 4) & 0xF0 | (b2 >>> 2); + } else { + bufOut[iOut++] = (b0 << 2) & 0xFC | (b1 >>> 4); + } + } + this.write(bufOut); + pr.disconnect(this); + }, + disconnect: function(f) { + let pr = pseudoRedirector; + pr.filters.delete(f); + f.disconnect(); + } + }; + + let onBeforeRequestClient = this.onBeforeRequest.callback; + let onBeforeRequest = function(details) { normalizeRequestDetails(details); - return onBeforeRequestClient(details); + let r = onBeforeRequestClient(details); + if ( + r !== undefined && + r.redirectUrl !== undefined && + details.type === 'xmlhttprequest' + ) { + r.redirectUrl = pseudoRedirector.start( + details.requestId, + r.redirectUrl + ); + } + return r; }; if ( onBeforeRequest ) { @@ -175,10 +250,10 @@ vAPI.net.registerListeners = function() { ); } - var onHeadersReceivedClient = this.onHeadersReceived.callback, + let onHeadersReceivedClient = this.onHeadersReceived.callback, onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0), onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes); - var onHeadersReceived = function(details) { + let onHeadersReceived = function(details) { normalizeRequestDetails(details); if ( onHeadersReceivedClientTypes.length !== 0 &&