mirror of
https://github.com/anticensority/runet-censorship-bypass.git
synced 2024-11-24 02:13:43 +03:00
Add incontinence/hunting mode for kitchen, amend utils and kitchen, refactor informer
This commit is contained in:
parent
bbd134b871
commit
a9eef144a9
|
@ -1,74 +1,139 @@
|
|||
'use strict';
|
||||
|
||||
const IF_DEBUG = false;
|
||||
{
|
||||
|
||||
if (!IF_DEBUG) {
|
||||
// I believe logging objects precludes them from being GCed.
|
||||
// I also don't remove logs for sake of client-side troubleshooting
|
||||
// (though no one sent me logs so far).
|
||||
['log', 'warn', 'error'].forEach( (meth) => {
|
||||
const _meth = window.console[meth].bind(console);
|
||||
window.console[meth] = function(...args) {
|
||||
const IF_DEBUG = false;
|
||||
|
||||
_meth(...args.map((a) => '' + a));
|
||||
if (!IF_DEBUG) {
|
||||
// I believe logging objects precludes them from being GCed.
|
||||
// I also don't remove logs for sake of client-side troubleshooting
|
||||
// (though no one sent me logs so far).
|
||||
['log', 'warn', 'error'].forEach( (meth) => {
|
||||
const _meth = window.console[meth].bind(console);
|
||||
window.console[meth] = function(...args) {
|
||||
|
||||
};
|
||||
});
|
||||
}
|
||||
_meth(...args.map((a) => '' + a));
|
||||
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
window.utils = {
|
||||
const self = window.utils = {
|
||||
|
||||
mandatory() {
|
||||
mandatory() {
|
||||
|
||||
throw new TypeError('Missing required argument. ' +
|
||||
'Be explicit if you swallow errors.');
|
||||
throw new TypeError('Missing required argument. ' +
|
||||
'Be explicit if you swallow errors.');
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
getProp(obj, path = this.mandatory()) {
|
||||
throwIfError(err) {
|
||||
|
||||
const props = path.split('.');
|
||||
if (!props.length) {
|
||||
throw new TypeError('Property must be supplied.');
|
||||
}
|
||||
const lastProp = props.pop();
|
||||
for( const prop of props ) {
|
||||
if (!(prop in obj)) {
|
||||
return undefined;
|
||||
if(err) {
|
||||
throw err;
|
||||
}
|
||||
obj = obj[prop];
|
||||
}
|
||||
return obj[lastProp];
|
||||
|
||||
},
|
||||
|
||||
areSettingsNotControlledFor(details) {
|
||||
|
||||
return ['controlled_by_other', 'not_controllable']
|
||||
.some( (prefix) => details.levelOfControl.startsWith(prefix) );
|
||||
|
||||
},
|
||||
|
||||
messages: {
|
||||
|
||||
searchSettingsForUrl(niddle) {
|
||||
|
||||
return 'chrome://settings/search#' + (chrome.i18n.getMessage(niddle) || niddle);
|
||||
|
||||
},
|
||||
|
||||
whichExtensionHtml() {
|
||||
checkChromeError(betterStack) {
|
||||
|
||||
return chrome.i18n.getMessage('noControl') +
|
||||
` <a href="${ this.searchSettingsForUrl('proxy') }">
|
||||
${ chrome.i18n.getMessage('which') }
|
||||
</a>`;
|
||||
// Chrome API calls your cb in a context different from the point of API
|
||||
// method invokation.
|
||||
const err = chrome.runtime.lastError || chrome.extension.lastError;
|
||||
if (err) {
|
||||
const args = ['API returned error:', err];
|
||||
if (betterStack) {
|
||||
args.push('\n' + betterStack);
|
||||
}
|
||||
console.warn(...args);
|
||||
}
|
||||
return err;
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
chromified(cb = self.mandatory(), ...replaceArgs) {
|
||||
|
||||
};
|
||||
const stack = (new Error()).stack;
|
||||
// Take error first callback and convert it to chrome api callback.
|
||||
return function(...args) {
|
||||
|
||||
window.apis = {};
|
||||
if (replaceArgs.length) {
|
||||
args = replaceArgs;
|
||||
}
|
||||
const err = self.checkChromeError(stack);
|
||||
// setTimeout fixes error context.
|
||||
setTimeout( cb.bind(null, err, ...args), 0 );
|
||||
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
getProp(obj, path = self.mandatory()) {
|
||||
|
||||
const props = path.split('.');
|
||||
if (!props.length) {
|
||||
throw new TypeError('Property must be supplied.');
|
||||
}
|
||||
const lastProp = props.pop();
|
||||
for( const prop of props ) {
|
||||
if (!(prop in obj)) {
|
||||
return undefined;
|
||||
}
|
||||
obj = obj[prop];
|
||||
}
|
||||
return obj[lastProp];
|
||||
|
||||
},
|
||||
|
||||
createStorage(prefix) {
|
||||
|
||||
return function state(key, value) {
|
||||
|
||||
key = prefix + key;
|
||||
if (value === null) {
|
||||
return localStorage.removeItem(key);
|
||||
}
|
||||
if (value === undefined) {
|
||||
const item = localStorage.getItem(key);
|
||||
return item && JSON.parse(item);
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
throw new TypeError('Converting Date format to JSON is not supported.');
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
areSettingsNotControlledFor(details) {
|
||||
|
||||
return ['controlled_by_other', 'not_controllable']
|
||||
.some( (prefix) => details.levelOfControl.startsWith(prefix) );
|
||||
|
||||
},
|
||||
|
||||
messages: {
|
||||
|
||||
searchSettingsForUrl(niddle) {
|
||||
|
||||
return 'chrome://settings/search#' + (chrome.i18n.getMessage(niddle) || niddle);
|
||||
|
||||
},
|
||||
|
||||
whichExtensionHtml() {
|
||||
|
||||
return chrome.i18n.getMessage('noControl') +
|
||||
` <a href="${ this.searchSettingsForUrl('proxy') }">
|
||||
${ chrome.i18n.getMessage('which') }
|
||||
</a>`;
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
window.apis = {};
|
||||
|
||||
}
|
||||
|
|
|
@ -47,23 +47,6 @@
|
|||
|
||||
}
|
||||
|
||||
const handlersState = function(key, value) {
|
||||
|
||||
key = 'handlers-' + key;
|
||||
if (value === null) {
|
||||
return localStorage.removeItem(key);
|
||||
}
|
||||
if (value === undefined) {
|
||||
const item = localStorage.getItem(key);
|
||||
return item && JSON.parse(item);
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
throw new TypeError('Converting Date format to JSON is not supported.');
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
|
||||
};
|
||||
|
||||
const openAndFocus = (url) => {
|
||||
|
||||
chrome.tabs.create(
|
||||
|
@ -78,6 +61,8 @@
|
|||
|
||||
window.apis.errorHandlers = {
|
||||
|
||||
state: window.utils.createStorage('handlers-'),
|
||||
|
||||
viewErrorVoid(type = window.utils.mandatory(), err) {
|
||||
|
||||
let errors = {};
|
||||
|
@ -113,14 +98,14 @@
|
|||
for(
|
||||
const name of (eventName ? [eventName] : this.getEventsMap().keys() )
|
||||
) {
|
||||
handlersState( ifPrefix + name, onOffStr === 'on' ? 'on' : null );
|
||||
this.state( ifPrefix + name, onOffStr === 'on' ? 'on' : null );
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
isOn(eventName) {
|
||||
|
||||
return handlersState( ifPrefix + eventName);
|
||||
return this.state( ifPrefix + eventName );
|
||||
|
||||
},
|
||||
|
||||
|
|
|
@ -2,21 +2,26 @@
|
|||
|
||||
{ // Private namespace starts.
|
||||
|
||||
const kitchenStorageKey = 'pac-kitchen-kept-mods';
|
||||
const mandatory = window.utils.mandatory;
|
||||
const throwIfError = window.utils.throwIfError;
|
||||
const chromified = window.utils.chromified;
|
||||
|
||||
const kitchenStartsMark = '\n\n//%#@@@@@@ PAC_KITCHEN_STARTS @@@@@@#%';
|
||||
const kitchenState = window.utils.createStorage('pac-kitchen-');
|
||||
const ifIncontinence = 'if-incontinence';
|
||||
|
||||
const configs = {
|
||||
|
||||
ifProxyHttpsUrlsOnly: {
|
||||
dflt: false,
|
||||
label: 'проксировать только HTTP<span style="border-bottom: 1px solid black">S</span>-сайты',
|
||||
desc: 'Проксировать только сайты, доступные по шифрованному протоколу HTTPS. Прокси и провайдер смогут видеть только адреса посещаемых вами ресурсов, но не их содержимое.',
|
||||
desc: 'Проксировать только сайты, доступные по шифрованному протоколу HTTPS. Прокси и провайдер смогут видеть только адреса проксируемых ресурсов, но не их содержимое.',
|
||||
index: 0,
|
||||
},
|
||||
ifUseSecureProxiesOnly: {
|
||||
dflt: false,
|
||||
label: 'только шифрованная связь с прокси',
|
||||
desc: 'Шифровать соединение до прокси от провайдера. Провайдер всё же сможет видеть адреса (но не содержимое) посещаемых вами ресурсов из протокола DNS.',
|
||||
desc: 'Шифровать соединение до прокси от провайдера. Провайдер всё же сможет видеть адреса (но не содержимое) проксируемых ресурсов из протокола DNS.',
|
||||
index: 1,
|
||||
},
|
||||
ifUsePacScriptProxies: {
|
||||
|
@ -53,11 +58,11 @@
|
|||
|
||||
const getCurrentConfigs = function getCurrentConfigs() {
|
||||
|
||||
const json = localStorage.getItem(kitchenStorageKey);
|
||||
if (!json) {
|
||||
const mods = kitchenState('mods');
|
||||
if (!mods) {
|
||||
return null;
|
||||
}
|
||||
return new PacModifiers(JSON.parse(json));
|
||||
return new PacModifiers(mods);
|
||||
|
||||
};
|
||||
|
||||
|
@ -122,7 +127,7 @@
|
|||
|
||||
getConfigs: getOrderedConfigsForUser,
|
||||
|
||||
cook(pacData, pacMods = window.utils.mandatory()) {
|
||||
cook(pacData, pacMods = mandatory()) {
|
||||
|
||||
return pacMods.ifNoMods ? pacData : pacData + `${ kitchenStartsMark }
|
||||
;+function(global) {
|
||||
|
@ -183,23 +188,20 @@
|
|||
|
||||
},
|
||||
|
||||
keepCookedNow(pacMods = window.utils.mandatory(), cb) {
|
||||
_tryNowAsync(details, cb = throwIfError) {
|
||||
|
||||
if (typeof(pacMods) === 'function') {
|
||||
cb = pacMods;
|
||||
const pacMods = getCurrentConfigs();
|
||||
if (!pacMods) {
|
||||
return cb(TypeError('PAC mods were never initialized.'));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
pacMods = new PacModifiers(pacMods);
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
localStorage.setItem(kitchenStorageKey, JSON.stringify(pacMods));
|
||||
if (typeof(details) === 'function') {
|
||||
cb = details;
|
||||
details = undefined;
|
||||
}
|
||||
chrome.proxy.settings.get({}, (details) => {
|
||||
|
||||
new Promise((resolve) =>
|
||||
|
||||
details
|
||||
? resolve(details)
|
||||
: chrome.proxy.settings.get({}, resolve)
|
||||
|
||||
).then( (details) => {
|
||||
|
||||
if (
|
||||
details.levelOfControl === 'controlled_by_this_extension'
|
||||
|
@ -211,28 +213,60 @@
|
|||
new RegExp(kitchenStartsMark + '[\\s\\S]*$', 'g'),
|
||||
''
|
||||
);
|
||||
return chrome.proxy.settings.set(details, cb);
|
||||
return chrome.proxy.settings.set(details, chromified(cb));
|
||||
}
|
||||
}
|
||||
return cb(
|
||||
null,
|
||||
null,
|
||||
[new TypeError('PAC-скрипт не обнаружен, но настройки будут активированы при установке PAC-скрипта.')]
|
||||
);
|
||||
|
||||
kitchenState(ifIncontinence, true);
|
||||
cb(new TypeError(
|
||||
'Не найдено активного PAC-скрипта! Изменения будут применены при возвращении контроля настроек прокси или установке нового PAC-скрипта.'
|
||||
));
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
checkIncontinence(details) {
|
||||
|
||||
if ( kitchenState(ifIncontinence) ) {
|
||||
this._tryNowAsync(details, () => {/* Swallow. */});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
keepCookedNowAsync(pacMods = mandatory(), cb = throwIfError) {
|
||||
|
||||
if (typeof(pacMods) === 'function') {
|
||||
cb = pacMods;
|
||||
const pacMods = getCurrentConfigs();
|
||||
if (!pacMods) {
|
||||
return cb(TypeError('PAC mods were never initialized and you haven\'t supplied any.'));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
pacMods = new PacModifiers(pacMods);
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
kitchenState('mods', pacMods);
|
||||
}
|
||||
this._tryNowAsync( (err) => cb(null, null, err && [err]) );
|
||||
|
||||
},
|
||||
|
||||
resetToDefaultsVoid() {
|
||||
|
||||
delete localStorage[kitchenStorageKey];
|
||||
this.keepCookedNow({});
|
||||
kitchenState('mods', null);
|
||||
kitchenState(ifIncontinence, null);
|
||||
this.keepCookedNowAsync({});
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const pacKitchen = window.apis.pacKitchen;
|
||||
|
||||
const originalSet = chrome.proxy.settings.set.bind( chrome.proxy.settings );
|
||||
|
||||
chrome.proxy.settings.set = function(details, cb) {
|
||||
|
@ -243,10 +277,18 @@
|
|||
}
|
||||
const pacMods = getCurrentConfigs();
|
||||
if (pacMods) {
|
||||
pac.data = window.apis.pacKitchen.cook( pac.data, pacMods );
|
||||
pac.data = pacKitchen.cook( pac.data, pacMods );
|
||||
}
|
||||
originalSet({ value: details.value }, cb);
|
||||
originalSet({ value: details.value }, (/* No args. */) => {
|
||||
|
||||
kitchenState(ifIncontinence, null);
|
||||
cb && cb();
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
pacKitchen.checkIncontinence();
|
||||
chrome.proxy.settings.onChange.addListener( pacKitchen.checkIncontinence.bind(pacKitchen) );
|
||||
|
||||
} // Private namespace ends.
|
||||
|
|
|
@ -22,14 +22,9 @@
|
|||
{ // Private namespace starts.
|
||||
|
||||
const mandatory = window.utils.mandatory;
|
||||
|
||||
const throwIfError = function throwIfError(err) {
|
||||
|
||||
if(err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
};
|
||||
const throwIfError = window.utils.throwIfError;
|
||||
const chromified = window.utils.chromified;
|
||||
const checkChromeError = window.utils.checkChromeError;
|
||||
|
||||
const asyncLogGroup = function asyncLogGroup(...args) {
|
||||
|
||||
|
@ -48,39 +43,6 @@
|
|||
|
||||
};
|
||||
|
||||
const checkChromeError = function checkChromeError(betterStack) {
|
||||
|
||||
// Chrome API calls your cb in a context different from the point of API
|
||||
// method invokation.
|
||||
const err = chrome.runtime.lastError || chrome.extension.lastError;
|
||||
if (err) {
|
||||
const args = ['API returned error:', err];
|
||||
if (betterStack) {
|
||||
args.push('\n' + betterStack);
|
||||
}
|
||||
console.warn(...args);
|
||||
}
|
||||
return err;
|
||||
|
||||
};
|
||||
|
||||
const chromified = function chromified(cb = mandatory(), ...replaceArgs) {
|
||||
|
||||
const stack = (new Error()).stack;
|
||||
// Take error first callback and convert it to chrome api callback.
|
||||
return function(...args) {
|
||||
|
||||
if (replaceArgs.length) {
|
||||
args = replaceArgs;
|
||||
}
|
||||
const err = checkChromeError(stack);
|
||||
// setTimeout fixes error context.
|
||||
setTimeout( cb.bind(null, err, ...args), 0 );
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
class Clarification {
|
||||
|
||||
constructor(message = mandatory(), prevClarification) {
|
|
@ -14,50 +14,38 @@
|
|||
Crazy parallel Chrome.
|
||||
**/
|
||||
|
||||
const antiCensorRu = window.apis.antiCensorRu;
|
||||
|
||||
window.chrome.browserAction.setBadgeBackgroundColor({
|
||||
color: '#db4b2f',
|
||||
});
|
||||
|
||||
window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
||||
|
||||
{
|
||||
|
||||
window.chrome.browserAction.setBadgeBackgroundColor({
|
||||
color: '#db4b2f',
|
||||
});
|
||||
|
||||
const _tabCallbacks = {};
|
||||
|
||||
function afterTabUpdated(tabId, cb) {
|
||||
if (_tabCallbacks[tabId])
|
||||
_tabCallbacks[tabId].push(cb);
|
||||
else _tabCallbacks[tabId] = [cb];
|
||||
}
|
||||
const afterTabUpdated = function afterTabUpdated(tabId, cb) {
|
||||
|
||||
if (_tabCallbacks[tabId]) {
|
||||
_tabCallbacks[tabId].push(cb);
|
||||
} else {
|
||||
_tabCallbacks[tabId] = [cb];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const onTabUpdate = function onTabUpdate(tabId) {
|
||||
|
||||
function onTabUpdate(tabId) {
|
||||
if (_tabCallbacks[tabId]) {
|
||||
_tabCallbacks[tabId].map( (f) => f() );
|
||||
delete _tabCallbacks[tabId];
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
chrome.tabs.onUpdated.addListener( onTabUpdate );
|
||||
|
||||
function isInsideTabWithIp(requestDetails) {
|
||||
return requestDetails.tabId !== -1 && requestDetails.ip;
|
||||
}
|
||||
const antiCensorRu = window.apis.antiCensorRu;
|
||||
|
||||
chrome.webRequest.onErrorOccurred.addListener(
|
||||
(requestDetails) =>
|
||||
isInsideTabWithIp(requestDetails)
|
||||
&& isProxiedAndInformed(requestDetails),
|
||||
{urls: ['<all_urls>']}
|
||||
);
|
||||
|
||||
chrome.tabs.onRemoved.addListener( (tabId) => {
|
||||
onTabUpdate(tabId);
|
||||
delete window.tabWithError2ip[tabId];
|
||||
});
|
||||
|
||||
function updateTitle(requestDetails, cb) {
|
||||
const updateTitle = function updateTitle(requestDetails, cb) {
|
||||
|
||||
chrome.browserAction.getTitle(
|
||||
{tabId: requestDetails.tabId},
|
||||
|
@ -74,6 +62,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
|||
const proxyTitle = 'Прокси:';
|
||||
|
||||
if (!ifTitleSetAlready) {
|
||||
|
||||
title = 'Разблокированы:\n' + indent + hostname + '\n'
|
||||
+ proxyTitle + '\n' + indent + proxyHost;
|
||||
ifShouldUpdateTitle = true;
|
||||
|
@ -84,6 +73,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
|||
});
|
||||
|
||||
} else {
|
||||
|
||||
const hostsProxiesPair = title.split(proxyTitle);
|
||||
|
||||
if (hostsProxiesPair[1].indexOf(proxyHost) === -1) {
|
||||
|
@ -95,6 +85,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
|||
}
|
||||
|
||||
if (hostsProxiesPair[0].indexOf(hostname) === -1) {
|
||||
|
||||
title = title.replace(
|
||||
proxyTitle,
|
||||
indent + hostname + '\n' + proxyTitle
|
||||
|
@ -119,6 +110,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
|||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (ifShouldUpdateTitle) {
|
||||
|
@ -132,11 +124,12 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
|||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let previousUpdateTitleFinished = Promise.resolve();
|
||||
|
||||
function isProxiedAndInformed(requestDetails) {
|
||||
const isProxiedAndInformed = function isProxiedAndInformed(requestDetails) {
|
||||
|
||||
if ( !(requestDetails.ip
|
||||
&& antiCensorRu.isProxied( requestDetails.ip )) ) {
|
||||
|
@ -156,7 +149,14 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
|||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const isInsideTabWithIp = function isInsideTabWithIp(requestDetails) {
|
||||
|
||||
return requestDetails.tabId !== -1 && requestDetails.ip;
|
||||
|
||||
};
|
||||
|
||||
chrome.webRequest.onResponseStarted.addListener(
|
||||
(requestDetails) => isInsideTabWithIp(requestDetails)
|
||||
|
@ -164,4 +164,11 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
|||
{urls: ['<all_urls>']}
|
||||
);
|
||||
|
||||
chrome.webRequest.onErrorOccurred.addListener(
|
||||
(requestDetails) =>
|
||||
isInsideTabWithIp(requestDetails)
|
||||
&& isProxiedAndInformed(requestDetails),
|
||||
{urls: ['<all_urls>']}
|
||||
);
|
||||
|
||||
}
|
||||
|
|
|
@ -48,6 +48,9 @@
|
|||
margin: 0.6em 0;
|
||||
padding: 0;
|
||||
}
|
||||
#none:checked + label {
|
||||
color: red;
|
||||
}
|
||||
#configs-panel > header {
|
||||
margin: 0 0 0.4em;
|
||||
}
|
||||
|
|
|
@ -79,10 +79,11 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
|
|||
const showErrors = (err, warns) => {
|
||||
|
||||
warns = warns || [];
|
||||
backgroundPage.console.log('eeeEEEEE',warns)
|
||||
const warning = warns
|
||||
.map(
|
||||
(w) => '✘ ' +
|
||||
(w.clarification && w.clarification.message || w.message || '')
|
||||
(w && w.clarification && w.clarification.message || w.message || '')
|
||||
)
|
||||
.join('<br/>');
|
||||
|
||||
|
@ -241,6 +242,7 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
|
|||
} else {
|
||||
li.innerHTML += `<a href="${conf.url}" class="info-sign info-url">🛈</a><br/>
|
||||
<textarea
|
||||
spellcheck="false"
|
||||
placeholder="SOCKS5 localhost:9050; # TOR Expert
|
||||
SOCKS5 localhost:9150; # TOR Browser
|
||||
HTTPS foobar.com:3143;
|
||||
|
|
Loading…
Reference in New Issue
Block a user