[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [meek/master] Add a Chrome packaged app and extension.
commit 41267cf007565d3fae1b0a5e2244dde55414dfae
Author: Chang Lan <changlan9@xxxxxxxxx>
Date: Thu Mar 27 17:59:50 2014 -0700
Add a Chrome packaged app and extension.
We use Chrome `webRequest` API to modify the `Host` header. However, this API is for extensions only. Chrome extensions cannot listen to sockets, which is supposed to be done by packaged apps. Therefore, the communication channel is a little bit hacky:
meek-client --(Socket)-- app --(Message)-- extension --(HTTPS)--
www.google.com
Caveat: the `webRequest` does not handle SPDY connection properly. We
need to disable SPDY in Chrome.
---
chrome/app/background.js | 124 ++++++++++++++++++++++++++++++++++++++++
chrome/app/manifest.json | 26 +++++++++
chrome/extension/background.js | 71 +++++++++++++++++++++++
chrome/extension/manifest.json | 19 ++++++
4 files changed, 240 insertions(+)
diff --git a/chrome/app/background.js b/chrome/app/background.js
new file mode 100644
index 0000000..818ba9e
--- /dev/null
+++ b/chrome/app/background.js
@@ -0,0 +1,124 @@
+// attempt to keep app from going inactive
+
+chrome.alarms.create("ping", {when: 5000, periodInMinutes: 1 });
+chrome.alarms.onAlarm.addListener(function(alarm) { console.info("alarm name = " + alarm.name); });
+
+const IP = "127.0.0.1";
+const PORT = 7000;
+const EXTENSION_ID = "epmfkpbifhkdhcedgfppmeeoonjenkee";
+
+const STATE_READING_LENGTH = 1;
+const STATE_READING_OBJECT = 2;
+const STATE_DONE = 3;
+
+var serverSocketId;
+var state = STATE_READING_LENGTH;
+var buf = new Uint8Array(4);
+var bytesToRead = buf.length;
+
+chrome.sockets.tcpServer.create({}, function(createInfo) {
+ listenAndAccept(createInfo.socketId);
+});
+
+function listenAndAccept(socketId) {
+ chrome.sockets.tcpServer.listen(socketId,
+ IP, PORT, function(resultCode) {
+ onListenCallback(socketId, resultCode)
+ });
+}
+
+function onListenCallback(socketId, resultCode) {
+ if (resultCode < 0) {
+ console.log("Error listening:" +
+ chrome.runtime.lastError.message);
+ return;
+ }
+ serverSocketId = socketId;
+ chrome.sockets.tcpServer.onAccept.addListener(onAccept);
+}
+
+function onAccept(info) {
+ if (info.socketId != serverSocketId)
+ return;
+ console.log("Client connected.");
+ chrome.sockets.tcp.onReceive.addListener(onReceive);
+ chrome.sockets.tcp.setPaused(info.clientSocketId, false);
+}
+
+function readIntoBuf(data) {
+ var n = Math.min(data.byteLength, bytesToRead);
+ buf.subarray(buf.length - bytesToRead, n).set(new Uint8Array(data.slice(0, n)));
+ bytesToRead -= n;
+ return data.slice(n);
+}
+
+function onReceive(info) {
+ console.log("Data received.");
+ var data = info.data;
+ switch (state) {
+ case STATE_READING_LENGTH:
+ data = readIntoBuf(data);
+ if (bytesToRead > 0)
+ return;
+
+ var b = buf;
+ bytesToRead = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
+ console.log(bytesToRead);
+ buf = new Uint8Array(bytesToRead);
+ state = STATE_READING_OBJECT;
+
+ case STATE_READING_OBJECT:
+ data = readIntoBuf(data);
+ if (bytesToRead > 0)
+ return;
+
+ var str = ab2str(buf);
+ console.log(str);
+ var request = JSON.parse(str);
+ makeRequest(request, info.socketId);
+
+ state = STATE_READING_LENGTH;
+ buf = new Uint8Array(4);
+ bytesToRead = buf.length;
+ }
+}
+
+function makeRequest(request, client_socket) {
+ chrome.runtime.sendMessage(EXTENSION_ID, request, function(response) {
+ returnResponse(response, client_socket);
+ });
+}
+
+function returnResponse(response, client_socket) {
+ var str = JSON.stringify(response);
+ var b = str2ab(str);
+
+ var buf = new Uint8Array(4 + b.byteLength);
+ var len = b.byteLength;
+ buf[0] = (len >> 24) & 0xff;
+ buf[1] = (len >> 16) & 0xff;
+ buf[2] = (len >> 8) & 0xff;
+ buf[3] = len & 0xff;
+ buf.set(new Uint8Array(b), 4);
+
+ chrome.sockets.tcp.send(client_socket, buf.buffer, function(info) {
+ if (info.resultCode != 0)
+ console.log("Send failed");
+ });
+}
+
+function ab2str(buffer) {
+ var encodedString = String.fromCharCode.apply(null, buffer),
+ decodedString = decodeURIComponent(escape(encodedString));
+ return decodedString;
+}
+
+function str2ab(string) {
+ var string = unescape(encodeURIComponent(string)),
+ charList = string.split(''),
+ buf = [];
+ for (var i = 0; i < charList.length; i++) {
+ buf.push(charList[i].charCodeAt(0));
+ }
+ return (new Uint8Array(buf)).buffer;
+}
diff --git a/chrome/app/manifest.json b/chrome/app/manifest.json
new file mode 100644
index 0000000..6c288d2
--- /dev/null
+++ b/chrome/app/manifest.json
@@ -0,0 +1,26 @@
+{
+ "manifest_version": 2,
+ "name": "meek-browser-app",
+ "minimum_chrome_version": "24",
+ "version": "0.1",
+
+ "permissions": [
+ "alarms"
+ ],
+
+ "sockets": {
+ "tcp": {
+ "connect": "*"
+ },
+ "tcpServer": {
+ "listen": "127.0.0.1:7000"
+ }
+ },
+
+ "app": {
+ "background": {
+ "scripts": ["background.js"],
+ "persistent": true
+ }
+ }
+}
diff --git a/chrome/extension/background.js b/chrome/extension/background.js
new file mode 100644
index 0000000..2e48212
--- /dev/null
+++ b/chrome/extension/background.js
@@ -0,0 +1,71 @@
+// attempt to keep app from going inactive
+
+chrome.alarms.create("ping", {when: 5000, periodInMinutes: 1 });
+chrome.alarms.onAlarm.addListener(function(alarm) { console.info("alarm name = " + alarm.name); });
+
+var host = 'meek-reflect.appspot.com';
+
+function onBeforeSendHeadersCallback(details) {
+ var did_set = false;
+ for (var i = 0; i < details.requestHeaders.length; ++i) {
+ if (details.requestHeaders[i].name === 'Host') {
+ details.requestHeaders[i].value = host;
+ did_set = true;
+ }
+ }
+ if (!did_set) {
+ details.requestHeaders.push({
+ name: 'Host',
+ value: host
+ });
+ }
+ return { requestHeaders: details.requestHeaders };
+}
+
+chrome.runtime.onMessageExternal.addListener(function(request, header, sendResponse) {
+ var timeout = 2000;
+ var xhr = new XMLHttpRequest();
+ xhr.ontimeout = function() {
+ console.error(url + "timed out.");
+ chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeadersCallback);
+ };
+ xhr.onerror = function() {
+ chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeadersCallback);
+ var response = { error: xhr.statusText };
+ sendResponse(response);
+ };
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeadersCallback);
+ var response = {status: xhr.status, body: xhr.responseText };
+ sendResponse(response);
+ }
+ };
+ var requestMethod = request.method;
+ var url = request.url;
+ xhr.open(requestMethod, url);
+ if (request.header != undefined) {
+ for (var key in request.header) {
+ if (key != "Host") { // TODO: Add more restricted header fields
+ xhr.setRequestHeader(key, request.header[key]);
+ } else {
+ host = request.header[key];
+ }
+ }
+ }
+ var body = null;
+ if (request.body != undefined) {
+ body = request.body;
+ }
+
+ chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeadersCallback, {
+ urls: [url],
+ types: ['xmlhttprequest']
+ }, ['requestHeaders', 'blocking']);
+
+ xhr.send(body);
+});
+
+function onReceiveXHR(xhr) {
+ console.log(xhr.responseText);
+}
diff --git a/chrome/extension/manifest.json b/chrome/extension/manifest.json
new file mode 100644
index 0000000..0e245db
--- /dev/null
+++ b/chrome/extension/manifest.json
@@ -0,0 +1,19 @@
+{
+ "manifest_version": 2,
+ "name": "meek-browser-extension",
+ "minimum_chrome_version": "24",
+ "version": "0.1",
+
+ "permissions": [
+ "notifications",
+ "alarms",
+ "webRequest",
+ "webRequestBlocking",
+ "<all_urls>"
+ ],
+
+ "background": {
+ "scripts": ["background.js"],
+ "persistent": true
+ }
+}
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits