/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
/**
* This test checks following expectations when using HTTP/1 proxy:
*
* - check we are seeing expected nsresult error codes on channels
* (nsIChannel.status) corresponding to different proxy status code
* responses (502, 504, 407, ...)
* - check we don't try to ask for credentials or otherwise authenticate to
* the proxy when 407 is returned and there is no Proxy-Authenticate
* response header sent
*/
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
const pps = Cc[
"@mozilla.org/network/protocol-proxy-service;1"].getService();
let server_port;
let http_server;
class ProxyFilter {
constructor(type, host, port, flags) {
this._type = type;
this._host = host;
this._port = port;
this._flags = flags;
this.QueryInterface = ChromeUtils.generateQI([
"nsIProtocolProxyFilter"]);
}
applyFilter(uri, pi, cb) {
if (uri.spec.match(/(\/proxy-session-counter)/)) {
cb.onProxyFilterResult(pi);
return;
}
cb.onProxyFilterResult(
pps.newProxyInfo(
this._type,
this._host,
this._port,
"",
"",
this._flags,
1000,
null
)
);
}
}
class UnxpectedAuthPrompt2 {
constructor(signal) {
this.signal = signal;
this.QueryInterface = ChromeUtils.generateQI([
"nsIAuthPrompt2"]);
}
asyncPromptAuth() {
this.signal.triggered =
true;
throw Components.Exception(
"", Cr.ERROR_UNEXPECTED);
}
}
class AuthRequestor {
constructor(prompt) {
this.prompt = prompt;
this.QueryInterface = ChromeUtils.generateQI([
"nsIInterfaceRequestor"]);
}
getInterface(iid) {
if (iid.equals(Ci.nsIAuthPrompt2)) {
return this.prompt();
}
throw Components.Exception(
"", Cr.NS_ERROR_NO_INTERFACE);
}
}
function make_channel(url) {
return NetUtil.newChannel({
uri: url,
loadUsingSystemPrincipal:
true,
// Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
});
}
function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL) {
return new Promise(resolve => {
channel.asyncOpen(
new ChannelListener(
(request, data) => {
request.QueryInterface(Ci.nsIHttpChannel);
const status = request.status;
const http_code = status ? undefined : request.responseStatus;
request.QueryInterface(Ci.nsIProxiedChannel);
const proxy_connect_response_code =
request.httpProxyConnectResponseCode;
resolve({ status, http_code, data, proxy_connect_response_code });
},
null,
flags
)
);
});
}
function connect_handler(request, response) {
Assert.equal(request.method,
"CONNECT");
switch (request.host) {
case "404.example.com":
response.setStatusLine(request.httpVersion, 404,
"Not found");
break;
case "407.example.com":
response.setStatusLine(request.httpVersion, 407,
"Authenticate");
// And deliberately no Proxy-Authenticate header
break;
case "429.example.com":
response.setStatusLine(request.httpVersion, 429,
"Too Many Requests");
break;
case "502.example.com":
response.setStatusLine(request.httpVersion, 502,
"Bad Gateway");
break;
case "504.example.com":
response.setStatusLine(request.httpVersion, 504,
"Gateway timeout");
break;
default:
response.setStatusLine(request.httpVersion, 500,
"I am dumb");
}
}
add_task(async
function setup() {
http_server =
new HttpServer();
http_server.identity.add(
"https",
"404.example.com", 443);
http_server.identity.add(
"https",
"407.example.com", 443);
http_server.identity.add(
"https",
"429.example.com", 443);
http_server.identity.add(
"https",
"502.example.com", 443);
http_server.identity.add(
"https",
"504.example.com", 443);
http_server.registerPathHandler(
"CONNECT", connect_handler);
http_server.start(-1);
server_port = http_server.identity.primaryPort;
// make all native resolve calls "secretly" resolve localhost instead
Services.prefs.setBoolPref(
"network.dns.native-is-localhost",
true);
pps.registerFilter(
new ProxyFilter(
"http",
"localhost", server_port, 0), 10);
});
registerCleanupFunction(() => {
Services.prefs.clearUserPref(
"network.dns.native-is-localhost");
});
/**
* Test series beginning.
*/
// The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
// code from the channel and not try to ask for any credentials.
add_task(async
function proxy_auth_failure() {
const chan = make_channel(`https:
//407.example.com/`);
const auth_prompt = { triggered:
false };
chan.notificationCallbacks =
new AuthRequestor(
() =>
new UnxpectedAuthPrompt2(auth_prompt)
);
const { status, http_code, proxy_connect_response_code } = await get_response(
chan,
CL_EXPECT_FAILURE
);
Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
Assert.equal(proxy_connect_response_code, 407);
Assert.equal(http_code, undefined);
Assert.equal(auth_prompt.triggered,
false,
"Auth prompt didn't trigger");
});
// 502 Bad gateway code returned by the proxy.
add_task(async
function proxy_bad_gateway_failure() {
const { status, http_code, proxy_connect_response_code } = await get_response(
make_channel(`https:
//502.example.com/`),
CL_EXPECT_FAILURE
);
Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
Assert.equal(proxy_connect_response_code, 502);
Assert.equal(http_code, undefined);
});
// 504 Gateway timeout code returned by the proxy.
add_task(async
function proxy_gateway_timeout_failure() {
const { status, http_code, proxy_connect_response_code } = await get_response(
make_channel(`https:
//504.example.com/`),
CL_EXPECT_FAILURE
);
Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
Assert.equal(proxy_connect_response_code, 504);
Assert.equal(http_code, undefined);
});
// 404 Not Found means the proxy could not resolve the host.
add_task(async
function proxy_host_not_found_failure() {
const { status, http_code, proxy_connect_response_code } = await get_response(
make_channel(`https:
//404.example.com/`),
CL_EXPECT_FAILURE
);
Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
Assert.equal(proxy_connect_response_code, 404);
Assert.equal(http_code, undefined);
});
// 429 Too Many Requests means we sent too many requests.
add_task(async
function proxy_too_many_requests_failure() {
const { status, http_code, proxy_connect_response_code } = await get_response(
make_channel(`https:
//429.example.com/`),
CL_EXPECT_FAILURE
);
Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS);
Assert.equal(proxy_connect_response_code, 429);
Assert.equal(http_code, undefined);
});
add_task(async
function shutdown() {
await
new Promise(resolve => {
http_server.stop(resolve);
});
});