summaryrefslogtreecommitdiffstats
path: root/src/scripts/import-proxy-list.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/scripts/import-proxy-list.js240
1 files changed, 240 insertions, 0 deletions
diff --git a/src/scripts/import-proxy-list.js b/src/scripts/import-proxy-list.js
new file mode 100644
index 0000000..a8bd838
--- /dev/null
+++ b/src/scripts/import-proxy-list.js
@@ -0,0 +1,240 @@
+'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`);
+ });
+ }
+}