Revision control
Copy as Markdown
Other Tools
/* 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
/**
* A proxy to convert HTTP requests to HTTPS for tests.
*/
const socketTransportService = Cc[
"@mozilla.org/network/socket-transport-service;1"
].getService(Ci.nsISocketTransportService);
/**
* @implements {nsIServerSocketListener}
*/
export class HttpsProxy {
QueryInterface = ChromeUtils.generateQI(["nsIServerSocketListener"]);
static async create(serverPort, tlsCertName, hostname) {
const tlsCert = await ServerTestUtils.getCertificate(tlsCertName);
const proxy = new HttpsProxy(serverPort, tlsCert);
NetworkTestUtils.configureProxy(hostname, 443, proxy.port);
return {
destroy() {
proxy.close();
NetworkTestUtils.unconfigureProxy(hostname, 443);
},
};
}
/**
* @type {HttpsProxyHandler[]}
*/
handlers = [];
/**
* @param {integer} serverPort - The port number of the HTTP server.
* @param {nsIX509Cert} tlsCert - The certificate to use for HTTPS requests.
* See ServerTestUtils.getCertificate.
*/
constructor(serverPort, tlsCert) {
this.serverPort = serverPort;
this.socket = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
Ci.nsITLSServerSocket
);
this.socket.init(-1, true, -1);
this.socket.serverCert = tlsCert;
this.socket.setSessionTickets(false);
this.socket.asyncListen(this);
dump(
`Reverse proxy from localhost:${this.serverPort} to localhost:${this.socket.port} opened\n`
);
TestUtils.promiseTestFinished?.then(() => {
this.close();
dump(
`Reverse proxy from localhost:${this.serverPort} to localhost:${this.socket.port} closed\n`
);
});
}
close() {
this.socket.close();
}
get port() {
return this.socket.port;
}
onSocketAccepted(socket, transport) {
const input = transport.openInputStream(0, 0, 0);
const output = transport.openOutputStream(0, 0, 0);
const handler = new HttpsProxyHandler(this, input, output);
const connectionInfo = transport.securityCallbacks.getInterface(
Ci.nsITLSServerConnectionInfo
);
connectionInfo.setSecurityObserver(handler);
this.handlers.push(handler);
}
onStopListening() {
for (const handler of this.handlers) {
handler.close();
}
this.handlers.length = 0;
}
}
/**
* @implements {nsIInputStreamCallback}
* @implements {nsITLSServerSecurityObserver}
*/
class HttpsProxyHandler {
QueryInterface = ChromeUtils.generateQI([
"nsIInputStreamCallback",
"nsITLSServerSecurityObserver",
]);
constructor(proxy, clientInputStream, clientOutputStream) {
this.proxy = proxy;
this.clientInputStream = clientInputStream;
this.clientOutputStream = clientOutputStream;
const transport = socketTransportService.createTransport(
[],
"localhost",
proxy.serverPort,
null,
null
);
this.serverInputStream = transport.openInputStream(0, 1024, 1024);
this.serverOutputStream = transport.openOutputStream(0, 1024, 1024);
}
close() {
this.clientInputStream.close();
this.clientOutputStream.close();
this.serverInputStream?.close();
this.serverOutputStream?.close();
}
onHandshakeDone() {
this.clientInputStream.asyncWait(this, 0, 0, Services.tm.currentThread);
}
async onInputStreamReady(clientInputStream) {
try {
const clientRequest =
CommonUtils.readBytesFromInputStream(clientInputStream);
let serverRequest = "";
const lines = clientRequest.split("\r\n");
serverRequest += `${lines[0]}\r\n`;
serverRequest += `Host: localhost:${this.proxy.serverPort}\r\n`;
for (let i = 1; i < lines.length; i++) {
if (
lines[i].startsWith("Authorization: ") ||
lines[i].startsWith("Content-Length: ") ||
lines[i].startsWith("Content-Type: ")
) {
serverRequest += `${lines[i]}\r\n`;
} else if (lines[i] == "") {
for (; i < lines.length; i++) {
serverRequest += `${lines[i]}\r\n`;
}
}
}
this.serverOutputStream.write(serverRequest, serverRequest.length);
NetUtil.asyncCopy(this.serverInputStream, this.clientOutputStream, () =>
this.close()
);
} catch (ex) {
console.error(ex.message);
}
}
}