tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_http1-proxy.js (7270B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /**
      8 * This test checks following expectations when using HTTP/1 proxy:
      9 *
     10 * - check we are seeing expected nsresult error codes on channels
     11 *   (nsIChannel.status) corresponding to different proxy status code
     12 *   responses (502, 504, 407, ...)
     13 * - check we don't try to ask for credentials or otherwise authenticate to
     14 *   the proxy when 407 is returned and there is no Proxy-Authenticate
     15 *   response header sent
     16 */
     17 
     18 "use strict";
     19 
     20 const { HttpServer } = ChromeUtils.importESModule(
     21  "resource://testing-common/httpd.sys.mjs"
     22 );
     23 
     24 const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
     25 
     26 let server_port;
     27 let http_server;
     28 
     29 class ProxyFilter {
     30  constructor(type, host, port, flags) {
     31    this._type = type;
     32    this._host = host;
     33    this._port = port;
     34    this._flags = flags;
     35    this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
     36  }
     37  applyFilter(uri, pi, cb) {
     38    if (uri.spec.match(/(\/proxy-session-counter)/)) {
     39      cb.onProxyFilterResult(pi);
     40      return;
     41    }
     42    cb.onProxyFilterResult(
     43      pps.newProxyInfo(
     44        this._type,
     45        this._host,
     46        this._port,
     47        "",
     48        "",
     49        this._flags,
     50        1000,
     51        null
     52      )
     53    );
     54  }
     55 }
     56 
     57 class UnxpectedAuthPrompt2 {
     58  constructor(signal) {
     59    this.signal = signal;
     60    this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]);
     61  }
     62  asyncPromptAuth() {
     63    this.signal.triggered = true;
     64    throw Components.Exception("", Cr.ERROR_UNEXPECTED);
     65  }
     66 }
     67 
     68 class AuthRequestor {
     69  constructor(prompt) {
     70    this.prompt = prompt;
     71    this.QueryInterface = ChromeUtils.generateQI(["nsIInterfaceRequestor"]);
     72  }
     73  getInterface(iid) {
     74    if (iid.equals(Ci.nsIAuthPrompt2)) {
     75      return this.prompt();
     76    }
     77    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
     78  }
     79 }
     80 
     81 function make_channel(url) {
     82  return NetUtil.newChannel({
     83    uri: url,
     84    loadUsingSystemPrincipal: true,
     85    // Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types
     86    contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
     87  });
     88 }
     89 
     90 function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL) {
     91  return new Promise(resolve => {
     92    channel.asyncOpen(
     93      new ChannelListener(
     94        (request, data) => {
     95          request.QueryInterface(Ci.nsIHttpChannel);
     96          const status = request.status;
     97          const http_code = status ? undefined : request.responseStatus;
     98          request.QueryInterface(Ci.nsIProxiedChannel);
     99          const proxy_connect_response_code =
    100            request.httpProxyConnectResponseCode;
    101          resolve({ status, http_code, data, proxy_connect_response_code });
    102        },
    103        null,
    104        flags
    105      )
    106    );
    107  });
    108 }
    109 
    110 function connect_handler(request, response) {
    111  Assert.equal(request.method, "CONNECT");
    112 
    113  switch (request.host) {
    114    case "404.example.com":
    115      response.setStatusLine(request.httpVersion, 404, "Not found");
    116      break;
    117    case "407.example.com":
    118      response.setStatusLine(request.httpVersion, 407, "Authenticate");
    119      // And deliberately no Proxy-Authenticate header
    120      break;
    121    case "429.example.com":
    122      response.setStatusLine(request.httpVersion, 429, "Too Many Requests");
    123      break;
    124    case "502.example.com":
    125      response.setStatusLine(request.httpVersion, 502, "Bad Gateway");
    126      break;
    127    case "504.example.com":
    128      response.setStatusLine(request.httpVersion, 504, "Gateway timeout");
    129      break;
    130    default:
    131      response.setStatusLine(request.httpVersion, 500, "I am dumb");
    132  }
    133 }
    134 
    135 add_task(async function setup() {
    136  http_server = new HttpServer();
    137  http_server.identity.add("https", "404.example.com", 443);
    138  http_server.identity.add("https", "407.example.com", 443);
    139  http_server.identity.add("https", "429.example.com", 443);
    140  http_server.identity.add("https", "502.example.com", 443);
    141  http_server.identity.add("https", "504.example.com", 443);
    142  http_server.registerPathHandler("CONNECT", connect_handler);
    143  http_server.start(-1);
    144  server_port = http_server.identity.primaryPort;
    145 
    146  // make all native resolve calls "secretly" resolve localhost instead
    147  Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
    148 
    149  pps.registerFilter(new ProxyFilter("http", "localhost", server_port, 0), 10);
    150 });
    151 
    152 registerCleanupFunction(() => {
    153  Services.prefs.clearUserPref("network.dns.native-is-localhost");
    154 });
    155 
    156 /**
    157 * Test series beginning.
    158 */
    159 
    160 // The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
    161 // code from the channel and not try to ask for any credentials.
    162 add_task(async function proxy_auth_failure() {
    163  const chan = make_channel(`https://407.example.com/`);
    164  const auth_prompt = { triggered: false };
    165  chan.notificationCallbacks = new AuthRequestor(
    166    () => new UnxpectedAuthPrompt2(auth_prompt)
    167  );
    168  const { status, http_code, proxy_connect_response_code } = await get_response(
    169    chan,
    170    CL_EXPECT_FAILURE
    171  );
    172 
    173  Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
    174  Assert.equal(proxy_connect_response_code, 407);
    175  Assert.equal(http_code, undefined);
    176  Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger");
    177 });
    178 
    179 // 502 Bad gateway code returned by the proxy.
    180 add_task(async function proxy_bad_gateway_failure() {
    181  const { status, http_code, proxy_connect_response_code } = await get_response(
    182    make_channel(`https://502.example.com/`),
    183    CL_EXPECT_FAILURE
    184  );
    185 
    186  Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
    187  Assert.equal(proxy_connect_response_code, 502);
    188  Assert.equal(http_code, undefined);
    189 });
    190 
    191 // 504 Gateway timeout code returned by the proxy.
    192 add_task(async function proxy_gateway_timeout_failure() {
    193  const { status, http_code, proxy_connect_response_code } = await get_response(
    194    make_channel(`https://504.example.com/`),
    195    CL_EXPECT_FAILURE
    196  );
    197 
    198  Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
    199  Assert.equal(proxy_connect_response_code, 504);
    200  Assert.equal(http_code, undefined);
    201 });
    202 
    203 // 404 Not Found means the proxy could not resolve the host.
    204 add_task(async function proxy_host_not_found_failure() {
    205  const { status, http_code, proxy_connect_response_code } = await get_response(
    206    make_channel(`https://404.example.com/`),
    207    CL_EXPECT_FAILURE
    208  );
    209 
    210  Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
    211  Assert.equal(proxy_connect_response_code, 404);
    212  Assert.equal(http_code, undefined);
    213 });
    214 
    215 // 429 Too Many Requests means we sent too many requests.
    216 add_task(async function proxy_too_many_requests_failure() {
    217  const { status, http_code, proxy_connect_response_code } = await get_response(
    218    make_channel(`https://429.example.com/`),
    219    CL_EXPECT_FAILURE
    220  );
    221 
    222  Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS);
    223  Assert.equal(proxy_connect_response_code, 429);
    224  Assert.equal(http_code, undefined);
    225 });
    226 
    227 add_task(async function shutdown() {
    228  await new Promise(resolve => {
    229    http_server.stop(resolve);
    230  });
    231 });