From 76dcdac3bd496a751312f5335cf3bbd78e9adcf6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 20:50:23 +0200 Subject: Adding upstream version 7.5.1+dfsg. Signed-off-by: Daniel Baumann --- src/scripts/background.js | 275 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 src/scripts/background.js (limited to 'src/scripts/background.js') diff --git a/src/scripts/background.js b/src/scripts/background.js new file mode 100644 index 0000000..a60546b --- /dev/null +++ b/src/scripts/background.js @@ -0,0 +1,275 @@ +'use strict'; + +// ----- global +//const FF = typeof browser !== 'undefined'; // for later +let storageArea; // keeping track of sync +let bgDisable = false; + +// Start in disabled mode because it's going to take time to load setings from storage +let activeSettings = {mode: 'disabled'}; + +// ----------------- logger -------------------------------- +let logger; +function getLog() { return logger; } +class Logger { + + constructor(size = 100, active = false) { + this.size = size; + this.matchedList = []; + this.unmatchedList = []; + this.active = active; + } + + clear() { + this.matchedList = []; + this.unmatchedList = []; + } + + addMatched(item) { + this.matchedList.push(item); + this.matchedList = this.matchedList.slice(-this.size); // slice to the ending size entries + } + + addUnmatched(item) { + this.unmatchedList.push(item); + this.unmatchedList = this.unmatchedList.slice(-this.size); // slice to the ending size entries + } + + updateStorage() { + this.matchedList = this.matchedList.slice(-this.size); // slice to the ending size entries + this.unmatchedList = this.unmatchedList.slice(-this.size); // slice to the ending size entries + storageArea.set({logging: {size: this.size, active: this.active} }); + } +} +// ----------------- /logger ------------------------------- + +// --- registering persistent listener +// https://bugzilla.mozilla.org/show_bug.cgi?id=1359693 ...Resolution: --- ? WONTFIX +chrome.webRequest.onAuthRequired.addListener(sendAuth, {urls: ['*://*/*']}, ['blocking']); +chrome.webRequest.onCompleted.addListener(clearPending, {urls: ['*://*/*']}); +chrome.webRequest.onErrorOccurred.addListener(clearPending, {urls: ['*://*/*']}); + +chrome.runtime.onInstalled.addListener((details) => { // Installs Update Listener + // reason: install | update | browser_update | shared_module_update + switch (true) { + + case details.reason === 'install': + case details.reason === 'update' && /^(3\.|4\.|5\.5|5\.6)/.test(details.previousVersion): + chrome.tabs.create({url: '/about.html?welcome'}); + break; + } +}); + +// ----------------- User Preference ----------------------- +chrome.storage.local.get(null, result => { + // browserVersion is not used & runtime.getBrowserInfo() is not supported on Chrome + // sync is NOT set or it is false, use this result ELSE get it from storage.sync + // check both storage on start-up + if (!Object.keys(result)[0]) { // local is empty, check sync + + chrome.storage.sync.get(null, syncResult => { + if (!Object.keys(syncResult)[0]) { // sync is also empty + storageArea = chrome.storage.local; // set storage as local + process(result); + } + else { + chrome.storage.local.set({sync: true}); // save sync as true + storageArea = chrome.storage.sync; // set storage as sync + process(syncResult); + } + }); + } + else { + storageArea = result.sync ? chrome.storage.sync : chrome.storage.local; // cache for subsequent use + !result.sync ? process(result) : chrome.storage.sync.get(null, process); + } +}); +// ----------------- /User Preference ---------------------- + +function process(settings) { + + let update; + let prefKeys = Object.keys(settings); + + if (!settings || !prefKeys[0]) { // create default settings if there are no settings + // default + settings = { + mode: 'disabled', + logging: { + size: 100, + active: false + } + }; + update = true; + } + + // update storage then add Change Listener + if (update) { + storageArea.set(settings, () => chrome.storage.onChanged.addListener(storageOnChanged)); + } + else { + chrome.storage.onChanged.addListener(storageOnChanged); + } + + logger = settings.logging ? new Logger(settings.logging.size, settings.logging.active) : new Logger(); + setActiveSettings(settings); + console.log('background.js: loaded proxy settings from storage.'); +} + +function storageOnChanged(changes, area) { +// console.log(changes); + // update storageArea on sync on/off change from options + if (changes.hasOwnProperty('sync') && changes.sync.newValue !== changes.sync.oldValue) { + storageArea = changes.sync.newValue ? chrome.storage.sync : chrome.storage.local; + } + + // update logger from log + if (Object.keys(changes).length === 1 && changes.logging) { return; } + + + // mode change from bg + if(changes.mode && changes.mode.newValue === 'disabled' && bgDisable) { + bgDisable = false; + return; + } + + // default: changes from popup | options + storageArea.get(null, setActiveSettings); +} + +function proxyRequest(requestInfo) { + return findProxyMatch(requestInfo.url, activeSettings); +} + +function setActiveSettings(settings) { + browser.proxy.onRequest.hasListener(proxyRequest) && browser.proxy.onRequest.removeListener(proxyRequest); + + const pref = settings; + const prefKeys = Object.keys(pref).filter(item => !['mode', 'logging', 'sync'].includes(item)); // not for these + + // --- cache credentials in authData (only those with user/pass) + prefKeys.forEach(id => pref[id].username && pref[id].password && + (authData[pref[id].address] = {username: pref[id].username, password: pref[id].password}) ); + + const mode = settings.mode; + activeSettings = { // global + mode, + proxySettings: [] + }; + + if (mode === 'disabled' || (FOXYPROXY_BASIC && mode === 'patterns')){ + setDisabled(); + return; + } + + if (['patterns', 'random', 'roundrobin'].includes(mode)) { // we only support 'patterns' ATM + + // filter out the inactive proxy settings + prefKeys.forEach(id => pref[id].active && activeSettings.proxySettings.push(pref[id])); + activeSettings.proxySettings.sort((a, b) => a.index - b.index); // sort by index + + function processPatternObjects(patternObjects) { + return patternObjects.reduce((accumulator, patternObject) => { + patternObject = Utils.processPatternObject(patternObject); + patternObject && accumulator.push(patternObject); + return accumulator; + }, []); + } + + // Filter out the inactive patterns. that way, each comparison + // is a little faster (doesn't even know about inactive patterns). Also convert all patterns to reg exps. + for (const idx in activeSettings.proxySettings) { + activeSettings.proxySettings[idx].blackPatterns = processPatternObjects(activeSettings.proxySettings[idx].blackPatterns); + activeSettings.proxySettings[idx].whitePatterns = processPatternObjects(activeSettings.proxySettings[idx].whitePatterns); + } + browser.proxy.onRequest.addListener(proxyRequest, {urls: [""]}); + Utils.updateIcon('images/icon.svg', null, 'patterns', true); + console.log(activeSettings, "activeSettings in patterns mode"); + } + else { + // User has selected a proxy for all URLs (not patterns, disabled, random, round-robin modes). + // mode is set to the proxySettings id to use for all URLs. + if (settings[mode]) { + activeSettings.proxySettings = [settings[mode]]; + browser.proxy.onRequest.addListener(proxyRequest, {urls: [""]}); + const tmp = Utils.getProxyTitle(settings[mode]); + Utils.updateIcon('images/icon.svg', settings[mode].color, tmp, false, tmp, false); + console.log(activeSettings, "activeSettings in fixed mode"); + } + else { + // This happens if user deletes the current proxy and mode is "use this proxy for all URLs" + // Don't remove this block. + bgDisable = true; + storageArea.set({mode: 'disabled'}); // only in case of error, otherwise mode is already set + setDisabled(); + console.error(`Error: mode is set to ${mode} but no active proxySetting is found with that id. Disabling Due To Error`); + } + } +} + + +function setDisabled(isError) { + browser.proxy.onRequest.hasListener(proxyRequest) && browser.proxy.onRequest.removeListener(proxyRequest); + chrome.runtime.sendMessage({mode: 'disabled'}); // Update the options.html UI if it's open + Utils.updateIcon('images/icon-off.svg', null, 'disabled', true); + console.log('******* disabled mode'); +} + + +// ----------------- Proxy Authentication ------------------ +// ----- session global +let authData = {}; +let authPending = {}; + +async function sendAuth(request) { + // Do nothing if this not proxy auth request: + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onAuthRequired + // "Take no action: the listener can do nothing, just observing the request. If this happens, it will + // have no effect on the handling of the request, and the browser will probably just ask the user to log in." + if (!request.isProxy) return; + + // --- already sent once and pending + if (authPending[request.requestId]) { return {cancel: true}; } + + // --- authData credentials not yet populated from storage + if(!Object.keys(authData)[0]) { await getAuth(request); } + + // --- first authentication + // According to https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onAuthRequired : + // "request.challenger.host is the requested host instead of the proxy requesting the authentication" + // But in my tests (Fx 69.0.1 MacOS), it is indeed the proxy requesting the authentication + // TODO: test in future Fx releases to see if that changes. + // console.log(request.challenger.host, "challenger host"); + if (authData[request.challenger.host]) { + authPending[request.requestId] = 1; // prevent bad authentication loop + return {authCredentials: authData[request.challenger.host]}; + } + // --- no user/pass set for the challenger.host, leave the authentication to the browser +} + +async function getAuth(request) { + + await new Promise(resolve => { + chrome.storage.local.get(null, result => { + const host = result.hostData[request.challenger.host]; + if (host && host.username) { // cache credentials in authData + authData[host] = {username: host.username, password: host.password}; + } + resolve(); + }); + }); +} + +function clearPending(request) { + + if(!authPending[request.requestId]) { return; } + + if (request.error) { + const host = request.proxyInfo && request.proxyInfo.host ? request.proxyInfo.host : request.ip; + Utils.notify(chrome.i18n.getMessage('authError', host)); + console.error(request.error); + return; // auth will be sent again + } + + delete authPending[request.requestId]; // no error +} \ No newline at end of file -- cgit v1.2.3