(function () { 'use strict'; /* * Copyright 2010-2020 Gildas Lormeau * contact : gildas.lormeau gmail.com * * This file is part of SingleFile. * * The code in this file is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * (GNU AGPL) as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * The code in this file 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 Affero * General Public License for more details. * * As additional permission under GNU AGPL version 3 section 7, you may * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU * AGPL normally required by section 4, provided you include this license * notice and a URL through which recipients can access the Corresponding * Source. */ /* global browser, XMLHttpRequest */ const referrers = new Map(); const REQUEST_ID_HEADER_NAME = "x-single-file-request-id"; const MAX_CONTENT_SIZE = 8 * (1024 * 1024); browser.runtime.onMessage.addListener((message, sender) => { if (message.method && message.method.startsWith("singlefile.fetch")) { return new Promise(resolve => { onRequest(message, sender) .then(resolve) .catch(error => resolve({ error: error && error.toString() })); }); } }); async function onRequest(message, sender) { if (message.method == "singlefile.fetch") { try { const response = await fetchResource(message.url, { referrer: message.referrer, headers: message.headers }); return sendResponse(sender.tab.id, message.requestId, response); } catch (error) { return sendResponse(sender.tab.id, message.requestId, { error: error.message, array: [] }); } } else if (message.method == "singlefile.fetchFrame") { return browser.tabs.sendMessage(sender.tab.id, message); } } async function sendResponse(tabId, requestId, response) { for (let blockIndex = 0; blockIndex * MAX_CONTENT_SIZE <= response.array.length; blockIndex++) { const message = { method: "singlefile.fetchResponse", requestId, headers: response.headers, status: response.status, error: response.error }; message.truncated = response.array.length > MAX_CONTENT_SIZE; if (message.truncated) { message.finished = (blockIndex + 1) * MAX_CONTENT_SIZE > response.array.length; message.array = response.array.slice(blockIndex * MAX_CONTENT_SIZE, (blockIndex + 1) * MAX_CONTENT_SIZE); } else { message.array = response.array; } await browser.tabs.sendMessage(tabId, message); } return {}; } function fetchResource(url, options = {}, includeRequestId) { return new Promise((resolve, reject) => { const xhrRequest = new XMLHttpRequest(); xhrRequest.withCredentials = true; xhrRequest.responseType = "arraybuffer"; xhrRequest.onerror = event => reject(new Error(event.detail)); xhrRequest.onreadystatechange = () => { if (xhrRequest.readyState == XMLHttpRequest.DONE) { if (xhrRequest.status || xhrRequest.response.byteLength) { if ((xhrRequest.status == 401 || xhrRequest.status == 403 || xhrRequest.status == 404) && !includeRequestId) { fetchResource(url, options, true) .then(resolve) .catch(reject); } else { resolve({ arrayBuffer: xhrRequest.response, array: Array.from(new Uint8Array(xhrRequest.response)), headers: { "content-type": xhrRequest.getResponseHeader("Content-Type") }, status: xhrRequest.status }); } } else { reject(new Error("Empty response")); } } }; xhrRequest.open("GET", url, true); if (options.headers) { for (const entry of Object.entries(options.headers)) { xhrRequest.setRequestHeader(entry[0], entry[1]); } } if (includeRequestId) { const randomId = String(Math.random()).substring(2); setReferrer(randomId, options.referrer); xhrRequest.setRequestHeader(REQUEST_ID_HEADER_NAME, randomId); } xhrRequest.send(); }); } function setReferrer(requestId, referrer) { referrers.set(requestId, referrer); } /* * Copyright 2010-2020 Gildas Lormeau * contact : gildas.lormeau gmail.com * * This file is part of SingleFile. * * The code in this file is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * (GNU AGPL) as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * The code in this file 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 Affero * General Public License for more details. * * As additional permission under GNU AGPL version 3 section 7, you may * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU * AGPL normally required by section 4, provided you include this license * notice and a URL through which recipients can access the Corresponding * Source. */ /* global browser */ browser.runtime.onMessage.addListener((message, sender) => { if (message.method == "singlefile.frameTree.initResponse" || message.method == "singlefile.frameTree.ackInitRequest") { browser.tabs.sendMessage(sender.tab.id, message, { frameId: 0 }); return Promise.resolve({}); } }); /* * Copyright 2010-2020 Gildas Lormeau * contact : gildas.lormeau gmail.com * * This file is part of SingleFile. * * The code in this file is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * (GNU AGPL) as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * The code in this file 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 Affero * General Public License for more details. * * As additional permission under GNU AGPL version 3 section 7, you may * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU * AGPL normally required by section 4, provided you include this license * notice and a URL through which recipients can access the Corresponding * Source. */ /* global browser, setTimeout, clearTimeout */ const timeouts = new Map(); browser.runtime.onMessage.addListener((message, sender) => { if (message.method == "singlefile.lazyTimeout.setTimeout") { let tabTimeouts = timeouts.get(sender.tab.id); let frameTimeouts; if (tabTimeouts) { frameTimeouts = tabTimeouts.get(sender.frameId); if (frameTimeouts) { const previousTimeoutId = frameTimeouts.get(message.type); if (previousTimeoutId) { clearTimeout(previousTimeoutId); } } else { frameTimeouts = new Map(); } } const timeoutId = setTimeout(async () => { try { const tabTimeouts = timeouts.get(sender.tab.id); const frameTimeouts = tabTimeouts.get(sender.frameId); if (tabTimeouts && frameTimeouts) { deleteTimeout(frameTimeouts, message.type); } await browser.tabs.sendMessage(sender.tab.id, { method: "singlefile.lazyTimeout.onTimeout", type: message.type }); } catch (error) { // ignored } }, message.delay); if (!tabTimeouts) { tabTimeouts = new Map(); frameTimeouts = new Map(); tabTimeouts.set(sender.frameId, frameTimeouts); timeouts.set(sender.tab.id, tabTimeouts); } frameTimeouts.set(message.type, timeoutId); return Promise.resolve({}); } if (message.method == "singlefile.lazyTimeout.clearTimeout") { let tabTimeouts = timeouts.get(sender.tab.id); if (tabTimeouts) { const frameTimeouts = tabTimeouts.get(sender.frameId); if (frameTimeouts) { const timeoutId = frameTimeouts.get(message.type); if (timeoutId) { clearTimeout(timeoutId); } deleteTimeout(frameTimeouts, message.type); } } return Promise.resolve({}); } }); browser.tabs.onRemoved.addListener(tabId => timeouts.delete(tabId)); function deleteTimeout(framesTimeouts, type) { framesTimeouts.delete(type); } })();