'use strict'; // ----------------- Internationalization ------------------ document.querySelectorAll('[data-i18n]').forEach(node => { let [text, attr] = node.dataset.i18n.split('|'); text = chrome.i18n.getMessage(text); attr ? node[attr] = text : node.appendChild(document.createTextNode(text)); }); // ----------------- /Internationalization ----------------- document.addEventListener('keyup', evt => { if (evt.keyCode === 27) { close(); } }); // ----------------- Spinner ------------------------------- const spinner = document.querySelector('.spinner'); function hideSpinner() { spinner.classList.remove('on'); setTimeout(() => { spinner.style.display = 'none'; }, 600); } function showSpinner() { spinner.style.display = 'flex'; spinner.classList.add('on'); } // ----------------- /spinner ------------------------------ hideSpinner(); // addEventListener for all buttons & handle together document.querySelectorAll('button').forEach(item => item.addEventListener('click', process)); document.querySelectorAll('input[type="file"]').forEach(item => item.addEventListener('change', process)); function process(e) { switch (this.id || this.dataset.i18n) { // click case 'back': close(); break; case 'export': Utils.exportFile(); break; case 'togglePW|title': const inp = this.previousElementSibling; inp.type = inp.type === 'password' ? 'text' : 'password'; break; // change case 'importFP': showSpinner(); foxyProxyImport(); break; case 'importJson': showSpinner(); Utils.importFile(e.target.files[0], ['application/json'], 1024*1024*10, 'json', importJson); // 10mb hideSpinner(); // hide spinner in case importJson() was not called due to error break; case 'importXml': showSpinner(); Utils.importFile(e.target.files[0], ['text/xml'], 1024*1024*10, 'xml', importXml); // 10mb hideSpinner(); // hide spinner in case importXml() was not called due to error break; } } function importJson(result) { if (!result) { // user cancelled hideSpinner(); return; } // --- convert pre v7.0 export to db format if (result.hasOwnProperty('proxySettings')) { result = prepareForStorage(result); } save(result, end); } function save(result, callback) { // Remove 'browserVersion', 'foxyProxyVersion', 'foxyProxyEdition' if they exist // We don't need those imported. delete result.browserVersion; delete result.foxyProxyVersion; delete result.foxyProxyEdition; const storageArea = result.sync ? chrome.storage.sync : chrome.storage.local; // clear the storages and set new chrome.storage.local.clear(() => chrome.storage.sync.clear(() => { if (result.sync) { chrome.storage.local.set({sync: true}); // save sync state delete result.sync; } storageArea.set(result, callback); // save to target })); } function end() { hideSpinner(); Utils.notify(chrome.i18n.getMessage('importEnd')); location.href = '/options.html'; } function importXml(doc) { let lastResortFound = false; // base format const pref = { mode: 'disabled', logging: { size: 100, active: false } }; const FP = doc.querySelector('foxyproxy'); if (!FP) { // Don't use Utils.notify() because at least on macOS, // the message is too long and cut off alert('There is an error with the XML file (missing )'); hideSpinner(); return; } const mode = FP.getAttribute('mode'); mode && (pref.mode = mode); const badModes = []; const proxies = doc.getElementsByTagName('proxy'); let patternsEdited = false; const LASTRESORT = 'k20d21508277536715'; const DEFAULT_PROXY_SETTING = { index: Number.MAX_SAFE_INTEGER, id: LASTRESORT, active: true, title: 'Default', notes: 'These are the settings that are used when no patterns match a URL.', color: '#0055E5', type: PROXY_TYPE_NONE, whitePatterns: [PATTERN_ALL_WHITE], blackPatterns: [] }; doc.querySelectorAll('proxy').forEach((item, index) => { const proxy = {}; // type a.k.a. mode const oldType = item.getAttribute('mode'); // Deactivate from patterns mode any unsupported types/modes const allowedType = ['manual', 'direct'].includes(oldType); proxy.active = allowedType ? item.getAttribute('enabled') === 'true' : false; // switch is faster than a series of if/else switch (oldType) { case 'system': badModes.push(item); proxy.type = PROXY_TYPE_SYSTEM; break; case 'auto': badModes.push(item); if (item.getAttribute('autoconfMode') === 'pac') { // PAC proxy.type = PROXY_TYPE_PAC; proxy.pacURL = item.querySelector('autoconf').getAttribute('url'); } else { // WPAD proxy.type = PROXY_TYPE_WPAD; proxy.pacURL = 'http://wpad/wpad.dat'; } break; case 'direct': proxy.type = PROXY_TYPE_NONE; break; case 'manual': const manualconf = item.querySelector('manualconf'); proxy.address = manualconf.getAttribute('host'); proxy.port = parseInt(manualconf.getAttribute('port')); proxy.username = manualconf.getAttribute('username'); proxy.password = manualconf.getAttribute('password'); // There appears to be a bug in 4.6.5 and possibly earlier versions: socksversion is always 5, never 4 if (manualconf.getAttribute('isSocks') === 'true') { proxy.type = PROXY_TYPE_SOCKS5; if (item.getAttribute('proxyDNS') === 'true') { proxy.proxyDNS = true; } } else if (manualconf.getAttribute('isHttps') === 'true') { proxy.type = PROXY_TYPE_HTTPS; } else { proxy.type = PROXY_TYPE_HTTP; } break; } proxy.title = item.getAttribute('name'); proxy.color = item.getAttribute('color'); let newId; const oldId = item.getAttribute('id'); if (item.getAttribute('lastresort') === 'true') { lastResortFound = true; newId = LASTRESORT; // this is a string proxy.index = Number.MAX_SAFE_INTEGER; if (!allowedType) { proxy.type = PROXY_TYPE_NONE; } } else { proxy.index = index; newId = 'import-' + oldId; } if (pref.mode === oldId) { // If the old top-level mode points to a proxy setting with an unsupported mode (e.g. WPAD), // we have to change the new top-level mode otherwise nothing will work w/o user intervention pref.mode = !allowedType ? PROXY_TYPE_NONE : newId; // Update mode to the new id ("import-" prefix) } proxy.whitePatterns = []; proxy.blackPatterns = []; item.querySelectorAll('match').forEach(mtch => { const newPattern = {}; /* "whitePatterns": [ { "title": "all URLs", "active": true, "pattern": "*", "type": 1, "protocols": 1 } ] */ newPattern.title = mtch.getAttribute('name'); newPattern.active = mtch.getAttribute('enabled') === 'true'; newPattern.importedPattern = newPattern.pattern = mtch.getAttribute('pattern'); newPattern.type = mtch.getAttribute('isRegEx') === 'true' ? PATTERN_TYPE_REGEXP : PATTERN_TYPE_WILDCARD; // Do some simple parsing but only for wildcards. Anything else is going to fail. if (newPattern.type === PATTERN_TYPE_WILDCARD) { switch (true) { case newPattern.pattern.startsWith('http://'): newPattern.protocols = PROTOCOL_HTTP; newPattern.pattern = newPattern.pattern.substring(7); break; case newPattern.pattern.startsWith('https://'): newPattern.protocols = PROTOCOL_HTTPS; newPattern.pattern = newPattern.pattern.substring(8); break; case newPattern.pattern.startsWith('*://'): newPattern.protocols = PROTOCOL_ALL; newPattern.pattern = newPattern.pattern.substring(4); break; default: newPattern.protocols = PROTOCOL_ALL; } // Clip everything after slashes; it can't be used anymore: https://bugzilla.mozilla.org/show_bug.cgi?id=1337001 const idx = newPattern.pattern.indexOf('/'); if (idx > -1) { newPattern.pattern = newPattern.pattern.substring(0, idx); patternsEdited = true; } } else { // e.g. ^https?://(?:[^:@/]+(?::[^@/]+)?@)?(?:localhost|127\.\d+\.\d+\.\d+)(?::\d+)?(?:/.*)?$ switch (true) { case newPattern.pattern.indexOf('^http://') === 1: newPattern.protocols = PROTOCOL_HTTP; newPattern.pattern = '^' + newPattern.pattern.substring(8); break; case newPattern.pattern.indexOf('^https://') === 1: newPattern.protocols = PROTOCOL_HTTPS; newPattern.pattern = '^' + newPattern.pattern.substring(9); break; case newPattern.pattern.indexOf('^https?://') === 1: newPattern.protocols = PROTOCOL_ALL; newPattern.pattern = '^' + newPattern.pattern.substring(10); break; default: newPattern.protocols = PROTOCOL_ALL; } } mtch.getAttribute('isBlackList') === 'true' ? proxy.blackPatterns.push(newPattern) : proxy.whitePatterns.push(newPattern); }); pref[newId] = proxy; }); if (!lastResortFound) { pref[LASTRESORT] = DEFAULT_PROXY_SETTING; } save(pref, () => endXML(patternsEdited)); } function endXML(patternsEdited) { hideSpinner(); if (patternsEdited) { // Don't use Utils.notify() because at least on macOS, // the message is too long and cut off alert(chrome.i18n.getMessage('patternsChanged')); location.href = '/options.html'; } else { // Don't use Utils.notify() because at least on macOS, // the message is too long and cut off alert(chrome.i18n.getMessage('importEndSlash')); location.href = '/options.html'; } } function prepareForStorage(settings) { if (!settings.hasOwnProperty('proxySettings') || !settings.proxySettings[0]) { alert('Imported file doesn not have any proxies.'); return null; } // base format const ret = { mode: 'disabled', logging: { size: 100, active: false } }; settings.mode && (ret.mode = settings.mode); settings.logging && (ret.logging = settings.logging); let idx = 0; settings.proxySettings.forEach(item => { const id = item.id; item.index = idx++; delete item.id; // Don't need id ret[id] = item; }); return ret; } // ----------------- FoxyProxy Import ---------------------- function foxyProxyImport() { // --- check user/pass const username = document.querySelector('#username').value.trim(); const password = document.querySelector('#password').value.trim(); if (!username || !password) { hideSpinner(); alert(chrome.i18n.getMessage('errorUserPass')); return; } // --- generate the form post data const usernamePassword = { 'username': username, 'password': password }; const formBody = []; for (const property in usernamePassword) { const encodedKey = encodeURIComponent(property); const encodedValue = encodeURIComponent(usernamePassword[property]); formBody.push(encodedKey + "=" + encodedValue); } // --- fetch data fetch('https://getfoxyproxy.org/webservices/get-accounts.php', { method: 'POST', body: formBody.join("&"), headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' } }) .then(response => response.json()) .then(response => { if (!Array.isArray(response) || !response[0] || !response[0].hostname) { hideSpinner(); Utils.notify(chrome.i18n.getMessage('errorFetch')); return; } const sync = localStorage.getItem('sync') === 'true'; const storageArea = !sync ? chrome.storage.local : chrome.storage.sync; storageArea.get(null, result => { response.forEach(item => { const hostname = item.hostname.substring(0, item.hostname.indexOf('.getfoxyproxy.org')); if (hostname && item.ipaddress && item.port && item.port[0] && item.country_code && item.country) { // --- creating proxy result[Math.random().toString(36).substring(7) + new Date().getTime()] = { index: -1, active: item.active, title: hostname, color: '#ff9900', type: 1, // HTTP address: item.ipaddress, port: item.port[0], username: item.username, password: item.password, cc: item.country_code, country: item.country, whitePatterns: [], blackPatterns: [] }; } }); storageArea.set(result, end); // save to target }); }) .catch(error => { hideSpinner(); Utils.notify(chrome.i18n.getMessage('errorFetch')); }); } // ----------------- /FoxyProxy Import --------------------- function close() { document.querySelector('#password').value = ''; /* prevent Firefox's save password prompt */ location.href = '/options.html'; }