231 lines
7.1 KiB
JavaScript
231 lines
7.1 KiB
JavaScript
/* -*- 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);
|
|
});
|
|
});
|