diff options
Diffstat (limited to 'ext/bg/js/request-builder.js')
-rw-r--r-- | ext/bg/js/request-builder.js | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/ext/bg/js/request-builder.js b/ext/bg/js/request-builder.js new file mode 100644 index 00000000..011d6bb8 --- /dev/null +++ b/ext/bg/js/request-builder.js @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +class RequestBuilder { + constructor() { + this._extraHeadersSupported = null; + this._onBeforeSendHeadersExtraInfoSpec = ['blocking', 'requestHeaders', 'extraHeaders']; + } + + async fetchAnonymous(url, init) { + const originURL = this._getOriginURL(url); + const modifications = [ + ['cookie', null], + ['origin', {name: 'Origin', value: originURL}] + ]; + return this.fetchModifyHeaders(url, init, modifications); + } + + async fetchModifyHeaders(url, init, modifications) { + const matchURL = this._getMatchURL(url); + + let done = false; + const callback = (details) => { + if (done || details.url !== url) { return {}; } + done = true; + + const requestHeaders = details.requestHeaders; + this._modifyHeaders(requestHeaders, modifications); + return {requestHeaders}; + }; + const filter = { + urls: [matchURL], + types: ['xmlhttprequest'] + }; + + let needsCleanup = false; + try { + this._onBeforeSendHeadersAddListener(callback, filter); + needsCleanup = true; + } catch (e) { + // NOP + } + + try { + return await fetch(url, init); + } finally { + if (needsCleanup) { + try { + chrome.webRequest.onBeforeSendHeaders.removeListener(callback); + } catch (e) { + // NOP + } + } + } + } + + // Private + + _onBeforeSendHeadersAddListener(callback, filter) { + const extraInfoSpec = this._onBeforeSendHeadersExtraInfoSpec; + for (let i = 0; i < 2; ++i) { + try { + chrome.webRequest.onBeforeSendHeaders.addListener(callback, filter, extraInfoSpec); + if (this._extraHeadersSupported === null) { + this._extraHeadersSupported = true; + } + 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 webRequest.onBeforeSendHeaders. + if (this._extraHeadersSupported !== null || !`${e.message}`.includes('extraHeaders')) { + throw e; + } + } + + // addListener failed; remove 'extraHeaders' from extraInfoSpec. + this._extraHeadersSupported = false; + const index = extraInfoSpec.indexOf('extraHeaders'); + if (index >= 0) { extraInfoSpec.splice(index, 1); } + } + } + + _getMatchURL(url) { + const url2 = new URL(url); + return `${url2.protocol}//${url2.host}${url2.pathname}`; + } + + _getOriginURL(url) { + const url2 = new URL(url); + return `${url2.protocol}//${url2.host}`; + } + + _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); + } + } + } +} |