tor-browser

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

support.sub.js (7571B)


      1 // Maps protocol (without the trailing colon) and address space to port.
      2 //
      3 // TODO(crbug.com/418737577): change keys to be consistent with new address
      4 // space names.
      5 const SERVER_PORTS = {
      6  "http": {
      7    "loopback": {{ports[http][0]}},
      8    "other-loopback": {{ports[http][1]}},
      9    "local": {{ports[http-local][0]}},
     10    "public": {{ports[http-public][0]}},
     11  },
     12  "https": {
     13    "loopback": {{ports[https][0]}},
     14    "other-loopback": {{ports[https][1]}},
     15    "local": {{ports[https-local][0]}},
     16    "public": {{ports[https-public][0]}},
     17  },
     18  "ws": {
     19    "loopback": {{ports[ws][0]}},
     20  },
     21  "wss": {
     22    "loopback": {{ports[wss][0]}},
     23  },
     24 };
     25 
     26 // A `Server` is a web server accessible by tests. It has the following shape:
     27 //
     28 // {
     29 //   addressSpace: the IP address space of the server ("loopback", "local" or
     30 //     "public"),
     31 //   name: a human-readable name for the server,
     32 //   port: the port on which the server listens for connections,
     33 //   protocol: the protocol (including trailing colon) spoken by the server,
     34 // }
     35 //
     36 // Constants below define the available servers, which can also be accessed
     37 // programmatically with `get()`.
     38 class Server {
     39  // Maps the given `protocol` (without a trailing colon) and `addressSpace` to
     40  // a server. Returns null if no such server exists.
     41  static get(protocol, addressSpace) {
     42    const ports = SERVER_PORTS[protocol];
     43    if (ports === undefined) {
     44      return null;
     45    }
     46 
     47    const port = ports[addressSpace];
     48    if (port === undefined) {
     49      return null;
     50    }
     51 
     52    return {
     53      addressSpace,
     54      name: `${protocol}-${addressSpace}`,
     55      port,
     56      protocol: protocol + ':',
     57    };
     58  }
     59 
     60  static HTTP_LOOPBACK = Server.get("http", "loopback");
     61  static OTHER_HTTP_LOOPBACK = Server.get("http", "other-loopback");
     62  static HTTP_LOCAL = Server.get("http", "local");
     63  static HTTP_PUBLIC = Server.get("http", "public");
     64  static HTTPS_LOOPBACK = Server.get("https", "loopback");
     65  static OTHER_HTTPS_LOOPBACK = Server.get("https", "other-loopback");
     66  static HTTPS_LOCAL = Server.get("https", "local");
     67  static HTTPS_PUBLIC = Server.get("https", "public");
     68  static WS_LOOPBACK = Server.get("ws", "loopback");
     69  static WSS_LOOPBACK = Server.get("wss", "loopback");
     70 };
     71 
     72 // Resolves a URL relative to the current location, returning an absolute URL.
     73 //
     74 // `url` specifies the relative URL, e.g. "foo.html" or "http://foo.example".
     75 // `options`, if defined, should have the following shape:
     76 //
     77 //   {
     78 //     // Optional. Overrides the protocol of the returned URL.
     79 //     protocol,
     80 //
     81 //     // Optional. Overrides the port of the returned URL.
     82 //     port,
     83 //
     84 //     // Extra headers.
     85 //     headers,
     86 //
     87 //     // Extra search params.
     88 //     searchParams,
     89 //   }
     90 //
     91 function resolveUrl(url, options) {
     92  const result = new URL(url, window.location);
     93  if (options === undefined) {
     94    return result;
     95  }
     96 
     97  const { port, protocol, headers, searchParams } = options;
     98  if (port !== undefined) {
     99    result.port = port;
    100  }
    101  if (protocol !== undefined) {
    102    result.protocol = protocol;
    103  }
    104  if (headers !== undefined) {
    105    const pipes = [];
    106    for (key in headers) {
    107      pipes.push(`header(${key},${headers[key]})`);
    108    }
    109    result.searchParams.append("pipe", pipes.join("|"));
    110  }
    111  if (searchParams !== undefined) {
    112    for (key in searchParams) {
    113      result.searchParams.append(key, searchParams[key]);
    114    }
    115  }
    116 
    117  return result;
    118 }
    119 
    120 // Computes options to pass to `resolveUrl()` for a source document's URL.
    121 //
    122 // `server` identifies the server from which to load the document.
    123 // `treatAsPublic`, if set to true, specifies that the source document should
    124 // be artificially placed in the `public` address space using CSP.
    125 function sourceResolveOptions({ server, treatAsPublic }) {
    126  const options = {...server};
    127  if (treatAsPublic) {
    128    options.headers = { "Content-Security-Policy": "treat-as-public-address" };
    129  }
    130  return options;
    131 }
    132 
    133 // Computes the URL of a target handler configured with the given options.
    134 //
    135 // `server` identifies the server from which to load the resource.
    136 // `behavior` specifies the behavior of the target server. It may contain:
    137 //   - `response`: The result of calling one of `ResponseBehavior`'s methods.
    138 //   - `redirect`: A URL to which the target should redirect GET requests.
    139 function resolveTargetUrl({ server, behavior }) {
    140  if (server === undefined) {
    141    throw new Error("no server specified.");
    142  }
    143  const options = {...server};
    144  if (behavior) {
    145    const { response, redirect } = behavior;
    146    options.searchParams = {
    147      ...response,
    148    };
    149    if (redirect !== undefined) {
    150      options.searchParams.redirect = redirect;
    151    }
    152  }
    153 
    154  return resolveUrl("target.py", options);
    155 }
    156 
    157 // Methods generate behavior specifications for how `resources/target.py`
    158 // should behave upon receiving a regular (non-preflight) request.
    159 const ResponseBehavior = {
    160  // The response should succeed without CORS headers.
    161  default: () => ({}),
    162 
    163  // The response should succeed with CORS headers.
    164  allowCrossOrigin: () => ({ "final-headers": "cors" }),
    165 };
    166 
    167 const FetchTestResult = {
    168  SUCCESS: {
    169    ok: true,
    170    body: "success",
    171  },
    172  OPAQUE: {
    173    ok: false,
    174    type: "opaque",
    175    body: "",
    176  },
    177  FAILURE: {
    178    error: "TypeError: Failed to fetch",
    179  },
    180 };
    181 
    182 // Helper function for checking results from fetch tests.
    183 function checkTestResult(actual, expected) {
    184  assert_equals(actual.error, expected.error, "error mismatch");
    185  assert_equals(actual.ok, expected.ok, "response ok mismatch");
    186  assert_equals(actual.body, expected.body, "response body mismatch");
    187 
    188  if (expected.type !== undefined) {
    189    assert_equals(type, expected.type, "response type mismatch");
    190  }
    191 }
    192 
    193 // Registers an event listener that will resolve this promise when this
    194 // window receives a message posted to it.
    195 function futureMessage(options) {
    196  return new Promise(resolve => {
    197    window.addEventListener('message', (e) => {
    198      resolve(e.data);
    199    });
    200  });
    201 };
    202 
    203 const NavigationTestResult = {
    204  SUCCESS: 'loaded',
    205  FAILURE: 'timeout',
    206 };
    207 
    208 async function iframeTest(
    209    t, {source, target, expected, permission = 'denied'}) {
    210  const targetUrl =
    211      resolveUrl('resources/openee.html', sourceResolveOptions(target));
    212 
    213  const sourceUrl =
    214      resolveUrl('resources/iframer.html', sourceResolveOptions(source));
    215  sourceUrl.searchParams.set('permission', permission);
    216  sourceUrl.searchParams.set('url', targetUrl);
    217 
    218  const popup = window.open(sourceUrl);
    219  t.add_cleanup(() => popup.close());
    220 
    221  // The child frame posts a message iff it loads successfully.
    222  // There exists no interoperable way to check whether an iframe failed to
    223  // load, so we use a timeout.
    224  // See: https://github.com/whatwg/html/issues/125
    225  const result = await Promise.race([
    226    futureMessage().then((data) => data.message),
    227    new Promise((resolve) => {
    228      t.step_timeout(() => resolve('timeout'), 2000 /* ms */);
    229    }),
    230  ]);
    231 
    232  assert_equals(result, expected);
    233 }
    234 
    235 async function navigateTest(t, {source, target, expected}) {
    236  const targetUrl =
    237      resolveUrl('resources/openee.html', sourceResolveOptions(target));
    238 
    239  const sourceUrl =
    240      resolveUrl('resources/navigate.html', sourceResolveOptions(source));
    241  sourceUrl.searchParams.set('url', targetUrl);
    242 
    243  const popup = window.open(sourceUrl);
    244  t.add_cleanup(() => popup.close());
    245 
    246  const result = await Promise.race([
    247    futureMessage().then((data) => data.message),
    248    new Promise((resolve) => {
    249      t.step_timeout(() => resolve('timeout'), 2000 /* ms */);
    250    }),
    251  ]);
    252 
    253  assert_equals(result, expected);
    254 }