diff options
Diffstat (limited to 'ext/js/background/request-builder.js')
-rw-r--r-- | ext/js/background/request-builder.js | 294 |
1 files changed, 85 insertions, 209 deletions
diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js index 663e242b..7ee89539 100644 --- a/ext/js/background/request-builder.js +++ b/ext/js/background/request-builder.js @@ -31,7 +31,6 @@ class RequestBuilder { * Creates a new instance. */ constructor() { - this._onBeforeSendHeadersExtraInfoSpec = ['blocking', 'requestHeaders', 'extraHeaders']; this._textEncoder = new TextEncoder(); this._ruleIds = new Set(); } @@ -42,6 +41,7 @@ class RequestBuilder { async prepare() { try { await this._clearDynamicRules(); + await this._clearSessionRules(); } catch (e) { // NOP } @@ -54,15 +54,50 @@ class RequestBuilder { * @returns {Promise<Response>} The response of the `fetch` call. */ async fetchAnonymous(url, init) { - if (isObject(chrome.declarativeNetRequest)) { - return await this._fetchAnonymousDeclarative(url, init); + const id = this._getNewRuleId(); + const originUrl = this._getOriginURL(url); + url = encodeURI(decodeURI(url)); + + this._ruleIds.add(id); + try { + const addRules = [{ + id, + priority: 1, + condition: { + urlFilter: `|${this._escapeDnrUrl(url)}|`, + resourceTypes: ['xmlhttprequest'] + }, + action: { + type: 'modifyHeaders', + requestHeaders: [ + { + operation: 'remove', + header: 'Cookie' + }, + { + operation: 'set', + header: 'Origin', + value: originUrl + } + ], + responseHeaders: [ + { + operation: 'remove', + header: 'Set-Cookie' + } + ] + } + }]; + + await this._updateSessionRules({addRules}); + try { + return await fetch(url, init); + } finally { + await this._tryUpdateSessionRules({removeRuleIds: [id]}); + } + } finally { + this._ruleIds.delete(id); } - const originURL = this._getOriginURL(url); - const headerModifications = [ - ['cookie', null], - ['origin', {name: 'Origin', value: originURL}] - ]; - return await this._fetchInternal(url, init, headerModifications); } /** @@ -125,145 +160,56 @@ class RequestBuilder { // Private - async _fetchInternal(url, init, headerModifications) { - const filter = { - urls: [this._getMatchURL(url)], - types: ['xmlhttprequest'] - }; - - let requestId = null; - const onBeforeSendHeadersCallback = (details) => { - if (requestId !== null || details.url !== url) { return {}; } - ({requestId} = details); - - if (headerModifications === null) { return {}; } - - const requestHeaders = details.requestHeaders; - this._modifyHeaders(requestHeaders, headerModifications); - return {requestHeaders}; - }; - - let errorDetailsTimer = null; - let {promise: errorDetailsPromise, resolve: errorDetailsResolve} = deferPromise(); - const onErrorOccurredCallback = (details) => { - if (errorDetailsResolve === null || details.requestId !== requestId) { return; } - if (errorDetailsTimer !== null) { - clearTimeout(errorDetailsTimer); - errorDetailsTimer = null; - } - errorDetailsResolve(details); - errorDetailsResolve = null; - }; - - const eventListeners = []; - const onBeforeSendHeadersExtraInfoSpec = (headerModifications !== null ? this._onBeforeSendHeadersExtraInfoSpec : []); - this._addWebRequestEventListener(chrome.webRequest.onBeforeSendHeaders, onBeforeSendHeadersCallback, filter, onBeforeSendHeadersExtraInfoSpec, eventListeners); - this._addWebRequestEventListener(chrome.webRequest.onErrorOccurred, onErrorOccurredCallback, filter, void 0, eventListeners); + async _clearSessionRules() { + const rules = await this._getSessionRules(); - try { - return await fetch(url, init); - } catch (e) { - // onErrorOccurred is not always invoked by this point, so a delay is needed - if (errorDetailsResolve !== null) { - errorDetailsTimer = setTimeout(() => { - errorDetailsTimer = null; - if (errorDetailsResolve === null) { return; } - errorDetailsResolve(null); - errorDetailsResolve = null; - }, 100); - } - const details = await errorDetailsPromise; - if (details !== null) { - const data = {details}; - this._assignErrorData(e, data); - } - throw e; - } finally { - this._removeWebRequestEventListeners(eventListeners); - } - } + if (rules.length === 0) { return; } - _addWebRequestEventListener(target, callback, filter, extraInfoSpec, eventListeners) { - try { - for (let i = 0; i < 2; ++i) { - try { - if (typeof extraInfoSpec === 'undefined') { - target.addListener(callback, filter); - } else { - target.addListener(callback, filter, extraInfoSpec); - } - break; - } catch (e) { - // Firefox doesn't support the 'extraHeaders' option and will throw the following error: - // Type error for parameter extraInfoSpec (Error processing 2: Invalid enumeration value "extraHeaders") for [target]. - if (i === 0 && `${e.message}`.includes('extraHeaders') && Array.isArray(extraInfoSpec)) { - const index = extraInfoSpec.indexOf('extraHeaders'); - if (index >= 0) { - extraInfoSpec.splice(index, 1); - continue; - } - } - throw e; - } - } - } catch (e) { - console.log(e); - return; + const removeRuleIds = []; + for (const {id} of rules) { + removeRuleIds.push(id); } - eventListeners.push({target, callback}); - } - _removeWebRequestEventListeners(eventListeners) { - for (const {target, callback} of eventListeners) { - try { - target.removeListener(callback); - } catch (e) { - console.log(e); - } - } + await this._updateSessionRules({removeRuleIds}); } - _getMatchURL(url) { - const url2 = new URL(url); - return `${url2.protocol}//${url2.host}${url2.pathname}${url2.search}`.replace(/\*/g, '%2a'); + _getSessionRules() { + return new Promise((resolve, reject) => { + chrome.declarativeNetRequest.getSessionRules((result) => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(result); + } + }); + }); } - _getOriginURL(url) { - const url2 = new URL(url); - return `${url2.protocol}//${url2.host}`; + _updateSessionRules(options) { + return new Promise((resolve, reject) => { + chrome.declarativeNetRequest.updateSessionRules(options, () => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(); + } + }); + }); } - _modifyHeaders(headers, modifications) { - modifications = new Map(modifications); - - for (let i = 0, ii = headers.length; i < ii; ++i) { - const header = headers[i]; - const name = header.name.toLowerCase(); - const modification = modifications.get(name); - if (typeof modification === 'undefined') { continue; } - - modifications.delete(name); - - if (modification === null) { - headers.splice(i, 1); - --i; - --ii; - } else { - headers[i] = modification; - } - } - - for (const header of modifications.values()) { - if (header !== null) { - headers.push(header); - } + async _tryUpdateSessionRules(options) { + try { + await this._updateSessionRules(options); + return true; + } catch (e) { + return false; } } async _clearDynamicRules() { - if (!isObject(chrome.declarativeNetRequest)) { return; } - - const rules = this._getDynamicRules(); + const rules = await this._getDynamicRules(); if (rules.length === 0) { return; } @@ -275,53 +221,6 @@ class RequestBuilder { await this._updateDynamicRules({removeRuleIds}); } - async _fetchAnonymousDeclarative(url, init) { - const id = this._getNewRuleId(); - const originUrl = this._getOriginURL(url); - url = encodeURI(decodeURI(url)); - - this._ruleIds.add(id); - try { - const addRules = [{ - id, - priority: 1, - condition: { - urlFilter: `|${this._escapeDnrUrl(url)}|`, - resourceTypes: ['xmlhttprequest'] - }, - action: { - type: 'modifyHeaders', - requestHeaders: [ - { - operation: 'remove', - header: 'Cookie' - }, - { - operation: 'set', - header: 'Origin', - value: originUrl - } - ], - responseHeaders: [ - { - operation: 'remove', - header: 'Set-Cookie' - } - ] - } - }]; - - await this._updateDynamicRules({addRules}); - try { - return await this._fetchInternal(url, init, null); - } finally { - await this._tryUpdateDynamicRules({removeRuleIds: [id]}); - } - } finally { - this._ruleIds.delete(id); - } - } - _getDynamicRules() { return new Promise((resolve, reject) => { chrome.declarativeNetRequest.getDynamicRules((result) => { @@ -348,15 +247,6 @@ class RequestBuilder { }); } - async _tryUpdateDynamicRules(options) { - try { - await this._updateDynamicRules(options); - return true; - } catch (e) { - return false; - } - } - _getNewRuleId() { let id = 1; while (this._ruleIds.has(id)) { @@ -367,6 +257,11 @@ class RequestBuilder { return id; } + _getOriginURL(url) { + const url2 = new URL(url); + return `${url2.protocol}//${url2.host}`; + } + _escapeDnrUrl(url) { return url.replace(/[|*^]/g, (char) => this._urlEncodeUtf8(char)); } @@ -380,25 +275,6 @@ class RequestBuilder { return result; } - _assignErrorData(error, data) { - try { - error.data = data; - } catch (e) { - // On Firefox, assigning DOMException.data can fail in certain contexts. - // https://bugzilla.mozilla.org/show_bug.cgi?id=1776555 - try { - Object.defineProperty(error, 'data', { - configurable: true, - enumerable: true, - writable: true, - value: data - }); - } catch (e2) { - // NOP - } - } - } - static _joinUint8Arrays(items, totalLength) { if (items.length === 1) { const {array, length} = items[0]; |