'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) { location.href = '/options.html'; } }); // ----------------- 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 ------------------------------ document.addEventListener('DOMContentLoaded', () => { hideSpinner(); }); // addEventListener for all buttons & handle together document.querySelectorAll('button').forEach(item => item.addEventListener('click', process)); let proxiesAdded = 0; // Global to this module in case user does multiple bulk imports before closing import-bulk.html function process(e) { switch (this.id || this.dataset.i18n) { case 'back': location.href = '/options.html'; break; case 'import': imp0rt(); break; } } function imp0rt() { const {parsedList, skippedList} = parseList(document.getElementById('proxyList').value); if (parsedList.length > 0) { if (document.querySelector('#overwrite').checked) { if (confirm(chrome.i18n.getMessage('confirmOverwrite'))) { showSpinner(); chrome.storage.local.clear(() => chrome.storage.sync.clear(() => { hideSpinner(); storeProxies(parsedList); })); } else { return; } } else { storeProxies(parsedList); } } if (skippedList.length > 0) { alert(`${chrome.i18n.getMessage('importsSkipped', [skippedList.length + "", skippedList.toString()])}`); } if (parsedList.length > 0) { alert(`${chrome.i18n.getMessage('importSucceeded', [parsedList.length])}`); } location.href = '/options.html'; } function parseList(rawList) { const parsedList = [], skippedList = [], colors = ['#663300', '#284B63', '#C99656', '#7B758C', '#171E1D']; if (!rawList) { return {parsedList, skippedList}; } rawList.split('\n').forEach((item) => { if (!item) { return; // continue to next } let p, patternIncludesAll = true, patternExcludesIntranet = true; // Is this line simple or complete format? let protocol = item.match(/.+:\/\//); // null for strings like 127.0.0.1:3128 (simple format) if (protocol) { // This line is uses 'complete' format let url; try { // In Firefox 78.0.2, the built-in javascript URL class will not parse URLs with custom schemes/protocols // like socks://127.0.0.1. However, Chrome 84.0.4147.89 and Node 14.5.0 both do. In order to be compatible // with Firefox, let's replace the scheme/protocol with 'http'. We could also instead write our own parsing // logic with a regular expression, but that does not seems necessary. if (protocol[0] !== 'http://' && protocol[0] !== 'https://') { item = 'http://' + item.substring(protocol[0].length); url = new URL(item); protocol = protocol[0].substring(0, protocol[0].length-2); //strip ending // } else { url = new URL(item); protocol = url.protocol; } } catch (e) { console.log(e); // URL couldn't be parsed skippedList.push(item); return; // continue to next } const type = protocol === 'proxy:' || protocol === 'http:' ? PROXY_TYPE_HTTP : protocol === 'ssl:' || protocol === 'https:' ? PROXY_TYPE_HTTPS : protocol === 'socks:' || protocol === 'socks5:' ? PROXY_TYPE_SOCKS5 : protocol === 'socks4:' ? PROXY_TYPE_SOCKS4 : -1; if (type === -1) { console.log("unknown protocol"); skippedList.push(item); return; // continue to next } // If color not specified in the URL, then rotate among the ones in the colors array. const color = url.searchParams.get('color') ? ('#' + url.searchParams.get('color')) : colors[parsedList.length % colors.length]; const title = url.searchParams.get('title'); const countryCode = url.searchParams.get('countryCode') || url.searchParams.get('cc'); const country = url.searchParams.get('country') || countryCode; // If paramName url param is not specified or it's specified and not 'false', then paramValue should equal true. // We assume true in case the param is absent, which may be counterintuitive, but this fcn is used for params that // we want to assume true when absent. function parseBooleanParam(url, paramName, aliasParamName) { const paramValue = url.searchParams.get(paramName) || (aliasParamName && url.searchParams.get(aliasParamName)); return paramValue ? !(paramValue.toLowerCase() === 'false') : true; } const proxyDNS = parseBooleanParam(url, 'proxyDns'); const active = parseBooleanParam(url, 'enabled', 'active'); patternIncludesAll = parseBooleanParam(url, 'patternIncludesAll'); patternExcludesIntranet = parseBooleanParam(url, 'patternExcludesIntranet'); // the URL class sets port === '' if not specified on the URL or it's an invalid port e.g. contains alpha chars let port = url.port; if (port === '') { // Default ports are 3128 for HTTP proxy, 443 for tls/ssl/https proxy, 1080 for socks4/5 port = type === PROXY_TYPE_HTTP ? 3128 : type === PROXY_TYPE_HTTPS ? 443 : 1080; } console.log(url); // the URL class sets username and password === '' if not specified on the URL p = {type, username: url.username, password: url.password, address: url.hostname, port, color, title, proxyDNS, active, countryCode, country}; } else { // simple const splitItem = item.split(':'); // Split always returns an array no matter what p = {address: splitItem[0], port: splitItem[1], username: splitItem[2], password: splitItem[3], color: colors[parsedList.length % colors.length]}; } const proxy = makeProxy(p, patternIncludesAll, patternExcludesIntranet); if (proxy) { parsedList.push(proxy); } else { skippedList.push(item); } }); //forEach return {parsedList, skippedList}; } function makeProxy({type = PROXY_TYPE_HTTP, username, password, address, port, color, title, proxyDNS, active = true, countryCode, country}, patternIncludesAll, patternExcludesIntranet) { port = port*1; // convert to digit if (!port || port < 1) { // is port NaN or less than 1 console.log("port is NaN or less than 1"); return null; } // strip bad chars from all input except username, password, type, proxyDNS, and active // (those last 3 are forced to boolean types before we are called) // If we do strip bad chars from usernams or password, auth could fail. address = Utils.stripBadChars(address); color = Utils.stripBadChars(color); title = Utils.stripBadChars(title); countryCode = Utils.stripBadChars(countryCode); country = Utils.stripBadChars(country); if (!address) { console.log("no address"); return null; } const proxy = {type, address, port, color, active}; // Only set the properties needed. null and undefined props seem to be saved if set, so don't set them. function setPropertyIfHasValue(prop, value, proxy) { if (value || value === 0) { proxy[prop] = value; } } setPropertyIfHasValue('username', username, proxy); setPropertyIfHasValue('password', password, proxy); setPropertyIfHasValue('title', title, proxy); setPropertyIfHasValue('cc', countryCode, proxy); setPropertyIfHasValue('country', country, proxy); if (type === PROXY_TYPE_SOCKS5) { // Only set if socks5 proxy.proxyDNS = proxyDNS; } if (FOXYPROXY_BASIC) { proxy.whitePatterns = proxy.blackPatterns = []; } else { proxy.whitePatterns = patternIncludesAll ? [PATTERN_ALL_WHITE] : []; proxy.blackPatterns = patternExcludesIntranet ? [...blacklistSet] : []; } return proxy; } function storeProxies(parsedList) { const sync = localStorage.getItem('sync') === 'true'; const storageArea = !sync ? chrome.storage.local : chrome.storage.sync; for (const idx in parsedList) { const proxy = parsedList[idx]; console.log(proxy); // Get the nextIndex given to us by options.js and add by the number of proxies we've added. // This ensures this proxy setting is last in list of all proxy settings. proxy.index = (localStorage.getItem('nextIndex')) + (++proxiesAdded); storageArea.set({[Utils.getUniqueId()]: proxy}, () => { console.log(`stored proxy`); }); } }