[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [meek/extension] Handle connections, do JSON, make requests.
commit 10e4dfeb23cddbb6d367d85f536b0d473a163b45
Author: David Fifield <david@xxxxxxxxxxxxxxx>
Date: Sat Mar 15 20:27:38 2014 -0700
Handle connections, do JSON, make requests.
---
firefox/components/main.js | 302 ++++++++++++++++++++++++++++++++++++++------
1 file changed, 263 insertions(+), 39 deletions(-)
diff --git a/firefox/components/main.js b/firefox/components/main.js
index c38eeda..8bda493 100644
--- a/firefox/components/main.js
+++ b/firefox/components/main.js
@@ -2,31 +2,20 @@
// https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/XPCOMUtils.jsm
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+// Everything resides within the MeekHTTPHelper namespace. MeekHTTPHelper is
+// also the type from which NSGetFactory is constructed, and it is the top-level
+// nsIServerSocketListener.
function MeekHTTPHelper() {
this.wrappedJSObject = this;
const LOCAL_PORT = 7000;
- // Create a "direct" nsIProxyInfo that bypasses the default proxy.
- // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIProtocolProxyService
- var pps = Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
- .getService(Components.interfaces.nsIProtocolProxyService);
- // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIProxyInfo
- this.directProxyInfo = pps.newProxyInfo("direct", "", 0, 0, 0xffffffff, null);
-
- // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIIOService
- this.ioService = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
- this.httpProtocolHandler = this.ioService.getProtocolHandler("http")
- .QueryInterface(Components.interfaces.nsIHttpProtocolHandler);
-
// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIServerSocket
var serverSocket = Components.classes["@mozilla.org/network/server-socket;1"]
.createInstance(Components.interfaces.nsIServerSocket);
serverSocket.init(LOCAL_PORT, true, -1);
serverSocket.asyncListen(this);
}
-
MeekHTTPHelper.prototype = {
classDescription: "meek HTTP helper component",
classID: Components.ID("{e7bc2b9c-f454-49f3-a19f-14848a4d871d}"),
@@ -39,51 +28,286 @@ MeekHTTPHelper.prototype = {
]),
// nsIServerSocketListener implementation.
- onSocketAccepted: function(aServ, aTransport) {
- dump("onSocketAccepted host " + aTransport.host + "\n");
+ onSocketAccepted: function(server, transport) {
+ dump("onSocketAccepted " + transport.host + ":" + transport.port + "\n");
+ new MeekHTTPHelper.LocalConnectionHandler(transport);
+ },
+ onStopListening: function(server, status) {
+ dump("onStopListening status " + status + "\n");
+ },
+};
- const FRONT_URL = "https://www.google.com/";
- const HOST = "meek-reflect.appspot.com";
+// Global variables and functions.
- var uri = this.ioService.newURI(FRONT_URL, null, null);
+MeekHTTPHelper.LOCAL_READ_TIMEOUT = 2.0;
+MeekHTTPHelper.LOCAL_WRITE_TIMEOUT = 2.0;
+
+// A "direct" nsIProxyInfo that bypasses the default proxy.
+// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIProtocolProxyService
+// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIProxyInfo
+MeekHTTPHelper.directProxyInfo = Components.classes["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService(Components.interfaces.nsIProtocolProxyService)
+ .newProxyInfo("direct", "", 0, 0, 0xffffffff, null);
+
+// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIIOService
+MeekHTTPHelper.ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+MeekHTTPHelper.httpProtocolHandler = MeekHTTPHelper.ioService.getProtocolHandler("http")
+ .QueryInterface(Components.interfaces.nsIHttpProtocolHandler);
+
+// Set the transport to time out at the given absolute deadline.
+MeekHTTPHelper.refreshDeadline = function(transport, deadline) {
+ var timeout;
+ if (deadline === null)
+ timeout = 0xffffffff;
+ else
+ timeout = Math.max(0.0, Math.ceil((deadline - Date.now()) / 1000.0));
+ transport.setTimeout(Components.interfaces.nsISocketTransport.TIMEOUT_READ_WRITE, timeout);
+};
+
+// Reverse-index the Components.results table.
+MeekHTTPHelper.lookupStatus = function(status) {
+ for (var name in Components.results) {
+ if (Components.results[name] === status)
+ return name;
+ }
+ return null;
+};
+
+// LocalConnectionHandler handles each new client connection received on the
+// socket opened by MeekHTTPHelper. It reads a JSON request, makes the request
+// on the Internet, and writes the result back to the socket. Error handling
+// happens within callbacks.
+MeekHTTPHelper.LocalConnectionHandler = function(transport) {
+ dump("LocalConnectionHandler\n");
+ this.transport = transport;
+ this.readRequest(this.makeRequest.bind(this));
+};
+MeekHTTPHelper.LocalConnectionHandler.prototype = {
+ readRequest: function(callback) {
+ dump("readRequest\n");
+ new MeekHTTPHelper.RequestReader(this.transport, this.makeRequest.bind(this));
+ },
+
+ makeRequest: function(req) {
+ dump("makeRequest " + JSON.stringify(req) + "\n");
+ if (!this.requestOk(req)) {
+ this.transport.close(0);
+ return;
+ }
+ var uri = MeekHTTPHelper.ioService.newURI(req.url, null, null);
// Construct an HTTP channel with the proxy bypass.
// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIHttpChannel
- var channel = this.httpProtocolHandler.newProxiedChannel(uri, this.directProxyInfo, 0, null)
+ var channel = MeekHTTPHelper.httpProtocolHandler.newProxiedChannel(uri, MeekHTTPHelper.directProxyInfo, 0, null)
.QueryInterface(Components.interfaces.nsIHttpChannel);
- // Set the host we really want.
- channel.setRequestHeader("Host", HOST, false);
- channel.redirectionLimit = 0;
+ if (req.header !== undefined) {
+ for (var key in req.header) {
+ dump("setting header " + key + ": " + req.header[key] + "\n");
+ channel.setRequestHeader(key, req.header[key], false);
+ }
+ }
+ if (req.body !== undefined) {
+ let body = atob(req.body);
+ let inputStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Components.interfaces.nsIStringInputStream);
+ inputStream.setData(body, body.length);
+ let uploadChannel = channel.QueryInterface(Components.interfaces.nsIUploadChannel);
+ uploadChannel.setUploadStream(inputStream, "application/octet-stream", body.length);
+ }
// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIUploadChannel
- // channel.requestMethod = "POST";
+ // says we must set requestMethod after calling setUploadStream.
+ channel.requestMethod = req.method;
+ channel.redirectionLimit = 0;
- channel.asyncOpen(new this.httpResponseStreamListener(), null);
+ channel.asyncOpen(new MeekHTTPHelper.HttpStreamListener(this.returnResponse.bind(this)), channel);
},
- onStopListening: function(aServ, aStatus) {
- dump("onStopListening status " + aStatus + "\n");
+
+ returnResponse: function(resp) {
+ dump("returnResponse " + JSON.stringify(resp) + "\n");
+ outputStream = this.transport.openOutputStream(
+ Components.interfaces.nsITransport.OPEN_BLOCKING | Components.interfaces.nsITransport.OPEN_UNBUFFERED, 0, 0);
+ var output = Components.classes["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryOutputStream);
+ output.setOutputStream(outputStream);
+
+ var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ var s = JSON.stringify(resp);
+ var data = converter.convertToByteArray(s);
+
+ var deadline = Date.now() + MeekHTTPHelper.LOCAL_WRITE_TIMEOUT * 1000;
+ try {
+ MeekHTTPHelper.refreshDeadline(this.transport, deadline);
+ output.write32(data.length);
+ MeekHTTPHelper.refreshDeadline(this.transport, deadline);
+ output.writeByteArray(data, data.length);
+ } finally {
+ output.close();
+ }
+ },
+
+ // Enforce restrictions on what requests we are willing to make. These can
+ // probably be loosened up. Try and rule out anything unexpected until we
+ // know we need otherwise.
+ requestOk: function(req) {
+ if (req.method === undefined) {
+ dump("req missing \"method\".\n");
+ return false;
+ }
+ if (req.url === undefined) {
+ dump("req missing \"url\".\n");
+ return false;
+ }
+
+ if (req.method !== "POST") {
+ dump("req.method is " + JSON.stringify(req.method) + ", not \"POST\".\n");
+ return false;
+ }
+ if (!req.url.startsWith("https://")) {
+ dump("req.url doesn't start with \"https://\".\n");
+ return false;
+ }
+
+ return true;
},
};
+// RequestReader reads a JSON-encoded request from the given transport, and
+// calls the given callback with the request as an argument. In case of error,
+// the transport is closed and the callback is not called.
+MeekHTTPHelper.RequestReader = function(transport, callback) {
+ dump("RequestReader\n");
+ this.transport = transport;
+ this.callback = callback;
-// https://developer.mozilla.org/en-US/docs/Creating_Sandboxed_HTTP_Connections
-MeekHTTPHelper.prototype.httpResponseStreamListener = function() {
+ this.curThread = Components.classes["@mozilla.org/thread-manager;1"].getService().currentThread;
+ this.inputStream = this.transport.openInputStream(
+ Components.interfaces.nsITransport.OPEN_BLOCKING | Components.interfaces.nsITransport.OPEN_UNBUFFERED, 0, 0);
+
+ this.state = this.STATE_READING_LENGTH;
+ this.buf = new Uint8Array(4);
+ this.bytesToRead = this.buf.length;
+ this.deadline = Date.now() + MeekHTTPHelper.LOCAL_READ_TIMEOUT * 1000;
+ this.asyncWait();
};
-MeekHTTPHelper.prototype.httpResponseStreamListener.prototype = {
+MeekHTTPHelper.RequestReader.prototype = {
+ STATE_READING_LENGTH: 1,
+ STATE_READING_OBJECT: 2,
+ STATE_DONE: 3,
+
+ // Do an asyncWait and handle the result.
+ asyncWait: function() {
+ MeekHTTPHelper.refreshDeadline(this.transport, this.deadline);
+ this.inputStream.asyncWait(this, 0, 0, this.curThread);
+ },
+
+ // nsIInputStreamCallback implementation.
+ onInputStreamReady: function(inputStream) {
+ var input = Components.classes["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Components.interfaces.nsIBinaryInputStream);
+ input.setInputStream(inputStream);
+ try {
+ switch (this.state) {
+ case this.STATE_READING_LENGTH:
+ this.doStateReadingLength(input);
+ this.asyncWait(inputStream);
+ break;
+ case this.STATE_READING_OBJECT:
+ this.doStateReadingObject(input);
+ if (this.bytesToRead > 0)
+ this.asyncWait(inputStream);
+ break;
+ }
+ } catch (e) {
+ dump("got exception " + e + "\n");
+ this.transport.close(0);
+ return;
+ }
+ },
+
+ // Read into this.buf (up to its capacity) and decrement this.bytesToRead.
+ readIntoBuf: function(input) {
+ var n = Math.min(input.available(), this.bytesToRead);
+ var data = input.readByteArray(n);
+ this.buf.subarray(this.buf.length - this.bytesToRead, n).set(data)
+ this.bytesToRead -= n;
+ },
+
+ doStateReadingLength: function(input) {
+ this.readIntoBuf(input);
+ if (this.bytesToRead > 0)
+ return;
+
+ this.state = this.STATE_READING_OBJECT;
+ var b = this.buf;
+ this.bytesToRead = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
+ if (this.bytesToRead > 1000000) {
+ dump("Object length is too long (" + this.bytesToRead + "), ignoring.\n");
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ this.buf = new Uint8Array(this.bytesToRead);
+ },
+
+ doStateReadingObject: function(input) {
+ this.readIntoBuf(input);
+ if (this.bytesToRead > 0)
+ return;
+
+ this.state = this.STATE_DONE;
+ var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ var s = converter.convertFromByteArray(this.buf, this.buf.length);
+ var req = JSON.parse(s);
+ this.callback(req);
+ },
+};
+
+// HttpStreamListener makes the requested HTTP request and calls the given
+// callback with a representation of the response. The "error" key of the return
+// value is defined if and only if there was an error.
+MeekHTTPHelper.HttpStreamListener = function(callback) {
+ this.callback = callback;
+ // This is a list of binary strings that is concatenated in onStopRequest.
+ this.body = [];
+ this.length = 0;
+};
+// https://developer.mozilla.org/en-US/docs/Creating_Sandboxed_HTTP_Connections
+MeekHTTPHelper.HttpStreamListener.prototype = {
// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIRequestObserver
- onStartRequest: function(aRequest, aContext) {
+ onStartRequest: function(req, context) {
dump("onStartRequest\n");
},
- onStopRequest: function(aRequest, aContext, aStatus) {
- dump("onStopRequest\n");
+ onStopRequest: function(req, context, status) {
+ dump("onStopRequest " + status + "\n");
+ var resp = {
+ status: context.responseStatus,
+ };
+ if (Components.isSuccessCode(status)) {
+ resp.body = btoa(this.body.join(""));
+ } else {
+ let err = MeekHTTPHelper.lookupStatus(status);
+ if (err !== null)
+ resp.error = err;
+ else
+ resp.error = "error " + String(status);
+ }
+ this.callback(resp);
},
+
// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIStreamListener
- onDataAvailable: function(aRequest, aContext, aStream, aSourceOffset, aLength) {
- dump("onDataAvailable\n");
- var a = new Uint8Array(aLength);
+ onDataAvailable: function(request, context, stream, sourceOffset, length) {
+ dump("onDataAvailable " + length + " bytes\n");
+ if (this.length + length > 1000000) {
+ request.cancel(Components.results.NS_ERROR_ILLEGAL_VALUE);
+ return;
+ }
+ this.length += length;
var input = Components.classes["@mozilla.org/binaryinputstream;1"]
.createInstance(Components.interfaces.nsIBinaryInputStream);
- input.setInputStream(aStream);
- input.readByteArray(aLength, a);
- dump(aLength + ":" + a + "\n");
+ input.setInputStream(stream);
+ this.body.push(String.fromCharCode.apply(null, input.readByteArray(length)));
},
};
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits