mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
8d1c3f1ede
Differential Revision: https://phabricator.services.mozilla.com/D212251
361 lines
9.6 KiB
JavaScript
361 lines
9.6 KiB
JavaScript
/* -*- 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() {},
|
|
|
|
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(false);
|
|
|
|
return chan;
|
|
}
|
|
|
|
function socketAccepted(socket1, 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() {
|
|
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();
|
|
}
|