diff --git a/EventEmitter.js b/EventEmitter.js
new file mode 100644
index 0000000..a56831c
--- /dev/null
+++ b/EventEmitter.js
@@ -0,0 +1,17 @@
+'use strict';
+
+var EventEmitter = function () {
+ this.ecb = {};
+};
+EventEmitter.prototype.emit = function (id, data) {
+ (this.ecb[id] || []).forEach(c => c(data));
+ chrome.runtime.sendMessage({
+ cmd: 'event',
+ id,
+ data
+ });
+};
+EventEmitter.prototype.on = function (id, callback) {
+ this.ecb[id] = this.ecb[id] || [];
+ this.ecb[id].push(callback);
+};
diff --git a/common.js b/common.js
new file mode 100644
index 0000000..885f29f
--- /dev/null
+++ b/common.js
@@ -0,0 +1,95 @@
+/* globals Tor, proxy, privacy, ui */
+'use strict';
+
+var prefs = {
+ webrtc: 2,
+ policy: {
+ 'proxy': 0, // 0: turn on when tor is active and turn off when tor is disabled; 1: turn on when browser starts and do not turn off when tor is disabled
+ 'webrtc': 0, // 0: turn on when tor is active and turn off when tor is disabled; 1: turn on when browser starts and do not turn off when tor is disabled
+ },
+ 'auto-run': false,
+ 'directory': null
+};
+chrome.storage.onChanged.addListener(ps => {
+ Object.keys(ps).forEach(p => prefs[p] = ps[p].newValue);
+});
+
+var tor = new Tor({
+ directory: ''
+});
+
+// get external IP address
+tor.on('status', status => {
+ ui.emit('title', {status});
+ if (status === 'connected') {
+ tor.getIP();
+ }
+});
+
+// Set proxy
+tor.on('status', s => {
+ if (s === 'connected') {
+ proxy.set(tor.info);
+ privacy.set(prefs.webrtc);
+ }
+ else if (s === 'disconnected') {
+ if (prefs.policy.proxy === 0) {
+ proxy.reset();
+ }
+ if (prefs.policy.webrtc === 0) {
+ privacy.reset();
+ }
+ }
+});
+// ip changes
+tor.on('ip', ip => ui.emit('title', {ip}));
+//
+proxy.addListener('change', bol => ui.emit('title', {
+ proxy: bol ? 'SOCKS' : 'default'
+}));
+chrome.storage.local.get(prefs, p => {
+ prefs = p;
+ // directory
+ tor.directory = p.directory;
+ // auto run?
+ if (prefs['auto-run']) {
+ tor.refresh();
+ }
+ if (prefs.policy.proxy === 1) {
+ privacy.set(prefs.proxy);
+ }
+ if (prefs.policy.webrtc === 1) {
+ privacy.set(prefs.webrtc);
+ }
+});
+// logs
+proxy.addListener('change', s => {
+ tor.emit('stdout', `Proxy status is "${s}"`);
+});
+privacy.addListener('change', (type, state) => {
+ tor.emit('stdout', `Protection: module -> ${type}, status -> ${state}`);
+});
+
+chrome.runtime.onMessage.addListener(request => {
+ if (request.method === 'popup-command') {
+ if (request.cmd) {
+ tor.command(request.cmd);
+ }
+ }
+ else if (request.method === 'popup-action') {
+ if (request.cmd === 'connection') {
+ if (request.action === 'disconnect') {
+ tor.disconnect();
+ }
+ else {
+ if (prefs.directory) {
+ tor.refresh();
+ }
+ else {
+ ui.notification('Tor Bundle path is not set in the options page');
+ chrome.runtime.openOptionsPage();
+ }
+ }
+ }
+ }
+});
diff --git a/data/helper b/data/helper
new file mode 120000
index 0000000..2b9b3a4
--- /dev/null
+++ b/data/helper
@@ -0,0 +1 @@
+../../external-application-button/data/helper/
\ No newline at end of file
diff --git a/data/icons/128.png b/data/icons/128.png
new file mode 100644
index 0000000..0e8725f
Binary files /dev/null and b/data/icons/128.png differ
diff --git a/data/icons/16.png b/data/icons/16.png
new file mode 100644
index 0000000..3653191
Binary files /dev/null and b/data/icons/16.png differ
diff --git a/data/icons/256.png b/data/icons/256.png
new file mode 100644
index 0000000..87c826d
Binary files /dev/null and b/data/icons/256.png differ
diff --git a/data/icons/32.png b/data/icons/32.png
new file mode 100644
index 0000000..cc30e4b
Binary files /dev/null and b/data/icons/32.png differ
diff --git a/data/icons/48.png b/data/icons/48.png
new file mode 100644
index 0000000..c024f34
Binary files /dev/null and b/data/icons/48.png differ
diff --git a/data/icons/512.png b/data/icons/512.png
new file mode 100644
index 0000000..1c29954
Binary files /dev/null and b/data/icons/512.png differ
diff --git a/data/icons/64.png b/data/icons/64.png
new file mode 100644
index 0000000..c3eeac2
Binary files /dev/null and b/data/icons/64.png differ
diff --git a/data/icons/enabled/128.png b/data/icons/enabled/128.png
new file mode 100644
index 0000000..8a874c7
Binary files /dev/null and b/data/icons/enabled/128.png differ
diff --git a/data/icons/enabled/16.png b/data/icons/enabled/16.png
new file mode 100644
index 0000000..6160a87
Binary files /dev/null and b/data/icons/enabled/16.png differ
diff --git a/data/icons/enabled/256.png b/data/icons/enabled/256.png
new file mode 100644
index 0000000..5a0884d
Binary files /dev/null and b/data/icons/enabled/256.png differ
diff --git a/data/icons/enabled/32.png b/data/icons/enabled/32.png
new file mode 100644
index 0000000..d5271cc
Binary files /dev/null and b/data/icons/enabled/32.png differ
diff --git a/data/icons/enabled/48.png b/data/icons/enabled/48.png
new file mode 100644
index 0000000..fd92408
Binary files /dev/null and b/data/icons/enabled/48.png differ
diff --git a/data/icons/enabled/512.png b/data/icons/enabled/512.png
new file mode 100644
index 0000000..c8b5b76
Binary files /dev/null and b/data/icons/enabled/512.png differ
diff --git a/data/icons/enabled/64.png b/data/icons/enabled/64.png
new file mode 100644
index 0000000..9b44d71
Binary files /dev/null and b/data/icons/enabled/64.png differ
diff --git a/data/options/index.html b/data/options/index.html
new file mode 100644
index 0000000..59efb72
--- /dev/null
+++ b/data/options/index.html
@@ -0,0 +1,29 @@
+
+
+
+ My Test Extension Options
+
+
+
+
+
+ Tor bundle path:
+
+
First download the latest "Tor Bundle" pack from
github.com, and extract it in a local directory. Then place the root's absolute path here.
+
+
+
+
+
+
+
+
+
+
diff --git a/data/options/index.js b/data/options/index.js
new file mode 100644
index 0000000..4031597
--- /dev/null
+++ b/data/options/index.js
@@ -0,0 +1,22 @@
+'use strict';
+
+function save () {
+ let directory = document.getElementById('directory').value;
+ chrome.storage.local.set({
+ directory
+ }, () => {
+ let status = document.getElementById('status');
+ status.textContent = 'Options saved.';
+ setTimeout(() => status.textContent = '', 750);
+ });
+}
+
+function restore () {
+ chrome.storage.local.get({
+ directory: '',
+ }, (prefs) => {
+ document.getElementById('directory').value = prefs.directory;
+ });
+}
+document.addEventListener('DOMContentLoaded', restore);
+document.getElementById('save').addEventListener('click', save);
diff --git a/data/popup/index.css b/data/popup/index.css
new file mode 100644
index 0000000..0f94cea
--- /dev/null
+++ b/data/popup/index.css
@@ -0,0 +1,74 @@
+body {
+ width: 500px;
+ height: 300px;
+}
+img {
+ cursor: pointer;
+}
+input[type=button] {
+ border: solid 1px #eee;
+ background-color: #fff;
+ width: 120px;
+ margin: 2px 0;
+ outline: none;
+ cursor: pointer;
+}
+input {
+ outline: none;
+}
+input[type=button]:active {
+ border-color: #00a;
+}
+
+[hbox] {
+ display: flex;
+ flex-direction: row;
+}
+[vbox] {
+ display: flex;
+ flex-direction: column;
+}
+[flex="1"] {
+ flex: 1;
+}
+[flex="2"] {
+ flex: 2;
+}
+[pack=center] {
+ justify-content: center;
+}
+[align=center] {
+ align-items: center;
+}
+body[data-status=connecting] img[data-cmd=connection] {
+ opacity: 0.3;
+}
+
+#log {
+ padding: 10px;
+ overflow-x: hidden;
+ overflow-y: auto;
+ width: calc(47vw - 20px);
+ background-color: rgba(0, 0, 0, 0.01);
+ border: dashed 1px rgba(0, 0, 0, 0.05);
+}
+#log>* {
+ margin-bottom: 10px;
+}
+#toolbar {
+ margin-top: 8px;
+}
+
+.msg span:nth-child(1) {
+ font-size: 80%;
+}
+.msg span:nth-child(2) {
+ padding: 0 5px;
+ background-color: rgba(0, 0, 0, 0.05);
+}
+
+.log {
+ background-color: rgba(0, 0, 0, 0.05);
+ border: dotted 1px rgba(0, 0, 0, 0.05);
+ padding: 0 5px;
+}
diff --git a/data/popup/index.html b/data/popup/index.html
new file mode 100644
index 0000000..987fd23
--- /dev/null
+++ b/data/popup/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/popup/index.js b/data/popup/index.js
new file mode 100644
index 0000000..07f6810
--- /dev/null
+++ b/data/popup/index.js
@@ -0,0 +1,73 @@
+'use strict';
+
+var elements = {
+ log: document.getElementById('log'),
+ template: document.querySelector('#log template'),
+ webrtc: document.getElementById('prefs.webrtc')
+};
+
+function log (msg) {
+ function single (msg) {
+ let node = document.importNode(elements.template.content, true);
+ let parts = /(.*)\[(err|warn|notice)\] (.*)/.exec(msg);
+ if (parts) {
+ node.querySelector('span:nth-child(1)').textContent = parts[1];
+ node.querySelector('span:nth-child(2)').textContent = parts[2];
+ node.querySelector('span:nth-child(3)').textContent = parts[3];
+ }
+ else {
+ node = document.createElement('span');
+ node.classList.add('log');
+ node.textContent = msg;
+ }
+
+ elements.log.appendChild(node);
+ elements.log.scrollTop = elements.log.scrollHeight;
+ }
+ msg.split('\n').filter(m => m.trim()).forEach(single);
+}
+
+function status (s) {
+ document.body.dataset.status = s;
+ document.querySelector('[data-cmd="connection"]').src =
+ s === 'disconnected' ? 'off.png' : 'on.png';
+}
+
+window.addEventListener('load', () => {
+ chrome.runtime.getBackgroundPage(b => {
+ log(b.tor.info.stdout);
+ status(b.tor.info.status);
+ });
+});
+
+chrome.runtime.onMessage.addListener(request => {
+ if (request.cmd === 'event' && request.id === 'stdout') {
+ log(request.data);
+ }
+ else if (request.cmd === 'event' && request.id === 'status') {
+ status(request.data);
+ }
+});
+
+document.addEventListener('click', e => {
+ let cmd = e.target.dataset.rcmd;
+ if (cmd) {
+ chrome.runtime.sendMessage({
+ method: 'popup-command',
+ cmd
+ });
+ }
+ cmd = e.target.dataset.cmd;
+ if (cmd === 'verify') {
+ chrome.tabs.create({
+ url: 'https://check.torproject.org/'
+ });
+ }
+ else if (cmd === 'connection') {
+ chrome.runtime.sendMessage({
+ method: 'popup-action',
+ cmd,
+ action: document.body.dataset.status === 'disconnected' ? 'connect' : 'disconnect'
+ });
+ }
+});
diff --git a/data/popup/off.png b/data/popup/off.png
new file mode 100644
index 0000000..66019bb
Binary files /dev/null and b/data/popup/off.png differ
diff --git a/data/popup/on.png b/data/popup/on.png
new file mode 100644
index 0000000..79d342a
Binary files /dev/null and b/data/popup/on.png differ
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..38b5614
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,47 @@
+{
+ "name": "Tor Browser",
+ "short_name": "itbrowser",
+ "description": "Enables Tor network and modify few settings to protect user privacy",
+ "author": "Jeremy Schomery",
+ "version": "0.1.0",
+ "manifest_version": 2,
+ "permissions": [
+ "storage",
+ "tabs",
+ "proxy",
+ "privacy",
+ "webRequest",
+ "downloads",
+ "management",
+ "notifications",
+ "nativeMessaging",
+ "https://api.github.com/repos/andy-portmen/native-client/releases/latest"
+ ],
+ "background": {
+ "scripts": [
+ "EventEmitter.js",
+ "tor.js",
+ "proxy.js",
+ "privacy.js",
+ "ui.js",
+ "common.js"
+ ]
+ },
+ "browser_action": {
+ "default_icon": {
+ "16": "data/icons/16.png",
+ "32": "data/icons/32.png"
+ },
+ "default_popup": "data/popup/index.html"
+ },
+ "homepage_url": "http://add0n.com/media-tools.html",
+ "icons": {
+ "16": "data/icons/16.png",
+ "48": "data/icons/48.png",
+ "128": "data/icons/128.png"
+ },
+ "options_ui": {
+ "page": "data/options/index.html",
+ "chrome_style": true
+ }
+}
diff --git a/privacy.js b/privacy.js
new file mode 100644
index 0000000..00f0602
--- /dev/null
+++ b/privacy.js
@@ -0,0 +1,38 @@
+'use strict';
+
+var privacy = {
+ onchanges: [],
+ modes: {
+ 0: 'default_public_and_private_interfaces',
+ 1: 'default_public_interface_only',
+ 2: 'disable_non_proxied_udp',
+ },
+ current: {
+ value: 'default'
+ },
+ set: (mode = 2, callback = function () {}) => {
+ chrome.privacy.network.webRTCIPHandlingPolicy.get({}, o => {
+ privacy.current = {
+ value: o.value
+ };
+
+ chrome.privacy.network.webRTCIPHandlingPolicy.set({
+ value: privacy.modes[mode]
+ }, () => {
+ privacy.onchanges.forEach(c => c('webrtc', true));
+ callback();
+ });
+ });
+ },
+ reset: (callback = function () {}) => {
+ chrome.privacy.network.webRTCIPHandlingPolicy.set(privacy.current, () => {
+ privacy.onchanges.forEach(c => c('webrtc', false));
+ callback();
+ });
+ },
+ addListener: (method, callback) => {
+ if (method === 'change') {
+ privacy.onchanges.push(callback);
+ }
+ }
+};
diff --git a/proxy.js b/proxy.js
new file mode 100644
index 0000000..28a2d29
--- /dev/null
+++ b/proxy.js
@@ -0,0 +1,49 @@
+'use strict';
+
+var proxy = {
+ onchanges: [],
+ current: {
+ value: {
+ mode: 'system'
+ }
+ },
+ set: (info, callback = function () {}) => {
+ let rule = {
+ host: info['socks-host'],
+ port: info['socks-port'],
+ scheme: 'socks5'
+ };
+ chrome.proxy.settings.get({}, o => {
+ proxy.current = {
+ value: o.value
+ };
+ console.log(proxy.current);
+
+ chrome.proxy.settings.set({
+ value: {
+ mode: 'fixed_servers',
+ rules: {
+ proxyForHttp: rule,
+ proxyForHttps: rule,
+ proxyForFtp: rule,
+ fallbackProxy: rule
+ }
+ }
+ }, () => {
+ proxy.onchanges.forEach(c => c(true));
+ callback();
+ });
+ });
+ },
+ reset: (callback = function () {}) => {
+ chrome.proxy.settings.set(proxy.current, () => {
+ proxy.onchanges.forEach(c => c(false));
+ callback();
+ });
+ },
+ addListener: (method, callback) => {
+ if (method === 'change') {
+ proxy.onchanges.push(callback);
+ }
+ }
+};
diff --git a/tor.js b/tor.js
new file mode 100644
index 0000000..01a3de5
--- /dev/null
+++ b/tor.js
@@ -0,0 +1,195 @@
+/* globals EventEmitter */
+'use strict';
+
+var Native = function () {
+ this.callback = null;
+ this.channel = chrome.runtime.connectNative('com.add0n.node');
+
+ function onDisconnect () {
+ chrome.tabs.create({
+ url: '/data/helper/index.html'
+ });
+ }
+
+ this.channel.onDisconnect.addListener(onDisconnect);
+ this.channel.onMessage.addListener(res => {
+ if (res && res.stdout && res.stdout.type === 'Buffer') {
+ res.stdout = {
+ data: String.fromCharCode.apply(String, res.stdout.data),
+ type: 'String'
+ };
+ }
+
+ if (!res) {
+ chrome.tabs.create({
+ url: '/data/helper/index.html'
+ });
+ }
+ else if (this.callback) {
+ this.callback(res);
+ }
+ });
+};
+Native.prototype.exec = function (command, args, callback = function () {}) {
+ this.callback = function (res) {
+ callback(res);
+ };
+ this.channel.postMessage({
+ cmd: 'exec',
+ command,
+ arguments: args
+ });
+};
+
+var Tor = function (options) {
+ this.callback = this.response;
+ EventEmitter.call(this);
+ this.directory = options.directory;
+ this.callbacks = [];
+
+ this.info = {
+ status: 'disconnected',
+ password: options.password || 'tor-browser',
+ stdout: 'Press the switch button to get started',
+ stderr: '',
+ progress: 0,
+ ip: '0.0.0.0',
+ 'socks-host': 'localhost',
+ 'socks-port': 22050,
+ 'control-port': 22051
+ };
+
+ this.on('stdout', m => {
+ this.info.stdout += m;
+ });
+ this.on('status', s => {
+ this.info.status = s;
+ });
+ this.on('progress', o => {
+ this.info.progress = o.value;
+ if (o.value === 100) {
+ this.emit('status', 'connected');
+ }
+ });
+};
+Tor.prototype = Object.create(Native.prototype);
+Tor.prototype = Object.create(EventEmitter.prototype);
+
+Tor.prototype.response = function (res) {
+ this.callbacks.forEach(c => c(res));
+ if (res.code) {
+ this.emit('status', 'disconnected');
+
+ if (res.code !== 0 && (res.code !== 1 || res.stderr !== '')) {
+ window.alert(`Something went wrong!
+
+-----
+Code: ${res.code}
+Output: ${res.stdout}
+Error: ${res.stderr}`
+ );
+ }
+ }
+
+ if (res.stdout) {
+ this.emit('stdout', res.stdout.data);
+ }
+ if (res.stderr) {
+ this.emit('stderr', res.stdout.data);
+ }
+
+ if (res.stdout) {
+ res.stdout.data.split('\n').forEach(data => {
+ let err = /\[(err|warn|notice)\] (.*)/.exec(data);
+ if (err) {
+ this.emit('console', {
+ type: err[1],
+ msg: err[2]
+ });
+ }
+
+ let progress = /Bootstrapped (\d+)%\: (.*)/.exec(data);
+ if (progress) {
+ this.emit('progress', {
+ value: +progress[1],
+ msg: progress[2]
+ });
+ }
+ });
+ }
+};
+
+Tor.prototype.connect = function () {
+ this.emit('status', 'connecting');
+ this.channel.postMessage({
+ cmd: 'spawn',
+ command: [this.directory, 'tor'],
+ arguments: ['-f', 'torrc'],
+ properties: {
+ detached: false,
+ cwd: this.directory
+ },
+ kill: true
+ });
+};
+
+Tor.prototype.refresh = function () {
+ Native.call(this);
+ this.callback = this.response;
+ this.stdout = 'Press the switch button to get started';
+ this.stderr = '';
+ this.connect();
+};
+
+Tor.prototype.command = function (command, callback = function () {}) {
+ let commands = [
+ `AUTHENTICATE "${this.info.password}"\r\n`, // Chapter 3.5
+ command + '\r\n',
+ 'QUIT\r\n'
+ ];
+ this.emit('stdout', 'Command: ' + command + '\n\r');
+
+ chrome.runtime.sendNativeMessage('com.add0n.node', {
+ cmd: 'net',
+ commands,
+ host: this.info['control-host'],
+ port: this.info['control-port'],
+ password: ''
+ }, res => {
+ callback(res);
+ this.emit('stdout', res.replace(/250[ \-\+]/g, '').replace(/\n\r?/g, '↵'));
+ });
+};
+
+Tor.prototype.disconnect = function () {
+ this.channel.disconnect();
+ this.emit('status', 'disconnected');
+ this.emit('stdout', 'Kill Tor instance\n\r');
+};
+var aaa;
+Tor.prototype.getIP = function (callback = function () {}) {
+ this.command('GETINFO circuit-status', res => {
+ aaa = res;
+ let id = (res || '')
+ .split('\n')
+ .filter(s => /^\d+\sBUILT/.test(s)).map(s => /\$([^\s\$\~]+)[\s\~][^\$]*$/.exec(s))
+ .filter(a => a)
+ .map(a => a[1]).shift();
+ if (id) {
+ this.command('GETINFO ns/id/' + id, res => {
+ let ip = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.exec(res || '');
+ if (ip) {
+ callback(ip[0]);
+ this.info.ip = ip[0];
+ this.emit('ip', ip[0]);
+ }
+ else {
+ callback();
+ }
+ });
+ }
+ else {
+ callback();
+ }
+ });
+};
diff --git a/ui.js b/ui.js
new file mode 100644
index 0000000..37d81c2
--- /dev/null
+++ b/ui.js
@@ -0,0 +1,43 @@
+/* globals EventEmitter */
+'use strict';
+
+var ui = new EventEmitter();
+ui.cache = {
+ status: 'disconnected',
+ ip: '0.0.0.0',
+ country: '-',
+ proxy: 'default'
+};
+
+ui.on('title', obj => {
+ ui.cache = Object.assign(ui.cache, obj);
+
+ chrome.browserAction.setTitle({
+ title: `Tor Browser (${ui.cache.status})
+
+IP: ${ui.cache.ip}
+Country: ${ui.cache.country}
+Proxy: ${ui.cache.proxy}`
+ });
+});
+ui.on('title', obj => {
+ if (obj.status) {
+ let active = obj.status === 'connected';
+ chrome.browserAction.setIcon({
+ path: {
+ 16: '/data/icons/' + (active ? 'enabled/' : '') + '16.png',
+ 32: '/data/icons/' + (active ? 'enabled/' : '') + '32.png',
+ 64: '/data/icons/' + (active ? 'enabled/' : '') + '64.png',
+ }
+ });
+ }
+});
+
+ui.notification = function (message) {
+ chrome.notifications.create({
+ type: 'basic',
+ title: 'Tor Protector',
+ message,
+ iconUrl: '/data/icons/48.png'
+ });
+};