summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/test_proxyconnect.js
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/test/unit/test_proxyconnect.js')
-rw-r--r--netwerk/test/unit/test_proxyconnect.js360
1 files changed, 360 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_proxyconnect.js b/netwerk/test/unit/test_proxyconnect.js
new file mode 100644
index 0000000000..a22bb25b31
--- /dev/null
+++ b/netwerk/test/unit/test_proxyconnect.js
@@ -0,0 +1,360 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// test_connectonly tests happy path of proxy connect
+// 1. CONNECT to localhost:socketserver_port
+// 2. Write 200 Connection established
+// 3. Write data to the tunnel (and read server-side)
+// 4. Read data from the tunnel (and write server-side)
+// 5. done
+// test_connectonly_noproxy tests an http channel with only connect set but
+// no proxy configured.
+// 1. OnTransportAvailable callback NOT called (checked in step 2)
+// 2. StopRequest callback called
+// 3. done
+// test_connectonly_nonhttp tests an http channel with only connect set with a
+// non-http proxy.
+// 1. OnTransportAvailable callback NOT called (checked in step 2)
+// 2. StopRequest callback called
+// 3. done
+
+// -1 then initialized with an actual port from the serversocket
+"use strict";
+
+var socketserver_port = -1;
+
+const CC = Components.Constructor;
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+const BinaryOutputStream = CC(
+ "@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream"
+);
+
+const STATE_NONE = 0;
+const STATE_READ_CONNECT_REQUEST = 1;
+const STATE_WRITE_CONNECTION_ESTABLISHED = 2;
+const STATE_CHECK_WRITE = 3; // write to the tunnel
+const STATE_CHECK_WRITE_READ = 4; // wrote to the tunnel, check connection data
+const STATE_CHECK_READ = 5; // read from the tunnel
+const STATE_CHECK_READ_WROTE = 6; // wrote to connection, check tunnel data
+const STATE_COMPLETED = 100;
+
+const CONNECT_RESPONSE_STRING = "HTTP/1.1 200 Connection established\r\n\r\n";
+const CHECK_WRITE_STRING = "hello";
+const CHECK_READ_STRING = "world";
+const ALPN = "webrtc";
+
+var connectRequest = "";
+var checkWriteData = "";
+var checkReadData = "";
+
+var threadManager;
+var socket;
+var streamIn;
+var streamOut;
+var accepted = false;
+var acceptedSocket;
+var state = STATE_NONE;
+var transportAvailable = false;
+var proxiedChannel;
+var listener = {
+ expectedCode: -1, // uninitialized
+
+ onStartRequest: function test_onStartR(request) {},
+
+ onDataAvailable: function test_ODA() {
+ do_throw("Should not get any data!");
+ },
+
+ onStopRequest: function test_onStopR(request, status) {
+ if (state === STATE_COMPLETED) {
+ Assert.equal(transportAvailable, false, "transport available not called");
+ Assert.equal(status, 0x80004005, "error code matches");
+ Assert.equal(proxiedChannel.httpProxyConnectResponseCode, 200);
+ nextTest();
+ return;
+ }
+
+ Assert.equal(accepted, true, "socket accepted");
+ accepted = false;
+ },
+};
+
+var upgradeListener = {
+ onTransportAvailable: (transport, socketIn, socketOut) => {
+ if (!transport || !socketIn || !socketOut) {
+ do_throw("on transport available failed");
+ }
+
+ if (state !== STATE_CHECK_WRITE) {
+ do_throw("bad state");
+ }
+
+ transportAvailable = true;
+
+ socketIn.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ socketOut.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIHttpUpgradeListener"]),
+};
+
+var connectHandler = {
+ onInputStreamReady: input => {
+ try {
+ const bis = new BinaryInputStream(input);
+ var data = bis.readByteArray(input.available());
+
+ dataAvailable(data);
+
+ if (state !== STATE_COMPLETED) {
+ input.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ }
+ } catch (e) {
+ do_throw(e);
+ }
+ },
+ onOutputStreamReady: output => {
+ writeData(output);
+ },
+ QueryInterface: iid => {
+ if (
+ iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIInputStreamCallback) ||
+ iid.equals(Ci.nsIOutputStreamCallback)
+ ) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+};
+
+function dataAvailable(data) {
+ switch (state) {
+ case STATE_READ_CONNECT_REQUEST:
+ connectRequest += String.fromCharCode.apply(String, data);
+ const headerEnding = connectRequest.indexOf("\r\n\r\n");
+ const alpnHeaderIndex = connectRequest.indexOf(`ALPN: ${ALPN}`);
+
+ if (headerEnding != -1) {
+ const requestLine = `CONNECT localhost:${socketserver_port} HTTP/1.1`;
+ Assert.equal(connectRequest.indexOf(requestLine), 0, "connect request");
+ Assert.equal(headerEnding, connectRequest.length - 4, "req head only");
+ Assert.notEqual(alpnHeaderIndex, -1, "alpn header found");
+
+ state = STATE_WRITE_CONNECTION_ESTABLISHED;
+ streamOut.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ }
+
+ break;
+ case STATE_CHECK_WRITE_READ:
+ checkWriteData += String.fromCharCode.apply(String, data);
+
+ if (checkWriteData.length >= CHECK_WRITE_STRING.length) {
+ Assert.equal(checkWriteData, CHECK_WRITE_STRING, "correct write data");
+
+ state = STATE_CHECK_READ;
+ streamOut.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ }
+
+ break;
+ case STATE_CHECK_READ_WROTE:
+ checkReadData += String.fromCharCode.apply(String, data);
+
+ if (checkReadData.length >= CHECK_READ_STRING.length) {
+ Assert.equal(checkReadData, CHECK_READ_STRING, "correct read data");
+
+ state = STATE_COMPLETED;
+
+ streamIn.asyncWait(null, 0, 0, null);
+ acceptedSocket.close(0);
+
+ nextTest();
+ }
+
+ break;
+ default:
+ do_throw("bad state: " + state);
+ }
+}
+
+function writeData(output) {
+ let bos = new BinaryOutputStream(output);
+
+ switch (state) {
+ case STATE_WRITE_CONNECTION_ESTABLISHED:
+ bos.write(CONNECT_RESPONSE_STRING, CONNECT_RESPONSE_STRING.length);
+ state = STATE_CHECK_WRITE;
+ break;
+ case STATE_CHECK_READ:
+ bos.write(CHECK_READ_STRING, CHECK_READ_STRING.length);
+ state = STATE_CHECK_READ_WROTE;
+ break;
+ case STATE_CHECK_WRITE:
+ bos.write(CHECK_WRITE_STRING, CHECK_WRITE_STRING.length);
+ state = STATE_CHECK_WRITE_READ;
+ break;
+ default:
+ do_throw("bad state: " + state);
+ }
+}
+
+function makeChan(url) {
+ if (!url) {
+ url = "https://localhost:" + socketserver_port + "/";
+ }
+
+ var flags =
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL |
+ Ci.nsILoadInfo.SEC_DONT_FOLLOW_REDIRECTS |
+ Ci.nsILoadInfo.SEC_COOKIES_OMIT;
+
+ var chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ securityFlags: flags,
+ });
+ chan = chan.QueryInterface(Ci.nsIHttpChannel);
+
+ var internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internal.HTTPUpgrade(ALPN, upgradeListener);
+ internal.setConnectOnly();
+
+ return chan;
+}
+
+function socketAccepted(socket, transport) {
+ accepted = true;
+
+ // copied from httpd.js
+ const SEGMENT_SIZE = 8192;
+ const SEGMENT_COUNT = 1024;
+
+ switch (state) {
+ case STATE_NONE:
+ state = STATE_READ_CONNECT_REQUEST;
+ break;
+ default:
+ return;
+ }
+
+ acceptedSocket = transport;
+
+ try {
+ streamIn = transport
+ .openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ streamOut = transport
+ .openOutputStream(0, 0, 0)
+ .QueryInterface(Ci.nsIAsyncOutputStream);
+
+ streamIn.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ } catch (e) {
+ transport.close(Cr.NS_BINDING_ABORTED);
+ do_throw(e);
+ }
+}
+
+function stopListening(socket, status) {
+ if (tests && tests.length !== 0 && do_throw) {
+ do_throw("should never stop");
+ }
+}
+
+function createProxy() {
+ try {
+ threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+ socket = new ServerSocket(-1, true, 1);
+ socketserver_port = socket.port;
+
+ socket.asyncListen({
+ onSocketAccepted: socketAccepted,
+ onStopListening: stopListening,
+ });
+ } catch (e) {
+ do_throw(e);
+ }
+}
+
+function test_connectonly() {
+ Services.prefs.setCharPref("network.proxy.ssl", "localhost");
+ Services.prefs.setIntPref("network.proxy.ssl_port", socketserver_port);
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ Services.prefs.setIntPref("network.proxy.type", 1);
+
+ var chan = makeChan();
+ proxiedChannel = chan.QueryInterface(Ci.nsIProxiedChannel);
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_connectonly_noproxy() {
+ clearPrefs();
+ var chan = makeChan();
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_connectonly_nonhttp() {
+ clearPrefs();
+
+ Services.prefs.setCharPref("network.proxy.socks", "localhost");
+ Services.prefs.setIntPref("network.proxy.socks_port", socketserver_port);
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+ Services.prefs.setIntPref("network.proxy.type", 1);
+
+ var chan = makeChan();
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function nextTest() {
+ transportAvailable = false;
+
+ if (!tests.length) {
+ do_test_finished();
+ return;
+ }
+
+ tests.shift()();
+ do_test_finished();
+}
+
+var tests = [
+ test_connectonly,
+ test_connectonly_noproxy,
+ test_connectonly_nonhttp,
+];
+
+function clearPrefs() {
+ Services.prefs.clearUserPref("network.proxy.ssl");
+ Services.prefs.clearUserPref("network.proxy.ssl_port");
+ Services.prefs.clearUserPref("network.proxy.socks");
+ Services.prefs.clearUserPref("network.proxy.socks_port");
+ Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ Services.prefs.clearUserPref("network.proxy.type");
+}
+
+function run_test() {
+ createProxy();
+
+ registerCleanupFunction(clearPrefs);
+
+ nextTest();
+ do_test_pending();
+}