[tor-commits] [meek/master] Add a Chrome packaged app and extension.

dcf at torproject.org dcf at torproject.org
Wed Apr 16 04:18:37 UTC 2014


commit 41267cf007565d3fae1b0a5e2244dde55414dfae
Author: Chang Lan <changlan9 at gmail.com>
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
+  }
+}





More information about the tor-commits mailing list