tor-browser

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

httpUtil.ts (3957B)


      1 /**
      2 * @license
      3 * Copyright 2023 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import {createWriteStream} from 'node:fs';
      8 import * as http from 'node:http';
      9 import * as https from 'node:https';
     10 import {URL, urlToHttpOptions} from 'node:url';
     11 
     12 import {ProxyAgent} from 'proxy-agent';
     13 
     14 export function headHttpRequest(url: URL): Promise<boolean> {
     15  return new Promise(resolve => {
     16    const request = httpRequest(
     17      url,
     18      'HEAD',
     19      response => {
     20        // consume response data free node process
     21        response.resume();
     22        resolve(response.statusCode === 200);
     23      },
     24      false,
     25    );
     26    request.on('error', () => {
     27      resolve(false);
     28    });
     29  });
     30 }
     31 
     32 export function httpRequest(
     33  url: URL,
     34  method: string,
     35  response: (x: http.IncomingMessage) => void,
     36  keepAlive = true,
     37 ): http.ClientRequest {
     38  const options: http.RequestOptions = {
     39    protocol: url.protocol,
     40    hostname: url.hostname,
     41    port: url.port,
     42    path: url.pathname + url.search,
     43    method,
     44    headers: keepAlive ? {Connection: 'keep-alive'} : undefined,
     45    auth: urlToHttpOptions(url).auth,
     46    agent: new ProxyAgent(),
     47  };
     48 
     49  const requestCallback = (res: http.IncomingMessage): void => {
     50    if (
     51      res.statusCode &&
     52      res.statusCode >= 300 &&
     53      res.statusCode < 400 &&
     54      res.headers.location
     55    ) {
     56      httpRequest(new URL(res.headers.location), method, response);
     57      // consume response data to free up memory
     58      // And prevents the connection from being kept alive
     59      res.resume();
     60    } else {
     61      response(res);
     62    }
     63  };
     64  const request =
     65    options.protocol === 'https:'
     66      ? https.request(options, requestCallback)
     67      : http.request(options, requestCallback);
     68  request.end();
     69  return request;
     70 }
     71 
     72 /**
     73 * @internal
     74 */
     75 export function downloadFile(
     76  url: URL,
     77  destinationPath: string,
     78  progressCallback?: (downloadedBytes: number, totalBytes: number) => void,
     79 ): Promise<void> {
     80  return new Promise<void>((resolve, reject) => {
     81    let downloadedBytes = 0;
     82    let totalBytes = 0;
     83 
     84    function onData(chunk: string): void {
     85      downloadedBytes += chunk.length;
     86      progressCallback!(downloadedBytes, totalBytes);
     87    }
     88 
     89    const request = httpRequest(url, 'GET', response => {
     90      if (response.statusCode !== 200) {
     91        const error = new Error(
     92          `Download failed: server returned code ${response.statusCode}. URL: ${url}`,
     93        );
     94        // consume response data to free up memory
     95        response.resume();
     96        reject(error);
     97        return;
     98      }
     99      const file = createWriteStream(destinationPath);
    100      file.on('finish', () => {
    101        return resolve();
    102      });
    103      file.on('error', error => {
    104        return reject(error);
    105      });
    106      response.pipe(file);
    107      totalBytes = parseInt(response.headers['content-length']!, 10);
    108      if (progressCallback) {
    109        response.on('data', onData);
    110      }
    111    });
    112    request.on('error', error => {
    113      return reject(error);
    114    });
    115  });
    116 }
    117 
    118 export async function getJSON(url: URL): Promise<unknown> {
    119  const text = await getText(url);
    120  try {
    121    return JSON.parse(text);
    122  } catch {
    123    throw new Error('Could not parse JSON from ' + url.toString());
    124  }
    125 }
    126 
    127 export function getText(url: URL): Promise<string> {
    128  return new Promise((resolve, reject) => {
    129    const request = httpRequest(
    130      url,
    131      'GET',
    132      response => {
    133        let data = '';
    134        if (response.statusCode && response.statusCode >= 400) {
    135          return reject(new Error(`Got status code ${response.statusCode}`));
    136        }
    137        response.on('data', chunk => {
    138          data += chunk;
    139        });
    140        response.on('end', () => {
    141          try {
    142            return resolve(String(data));
    143          } catch {
    144            return reject(new Error('Chrome version not found'));
    145          }
    146        });
    147      },
    148      false,
    149    );
    150    request.on('error', err => {
    151      reject(err);
    152    });
    153  });
    154 }