tor-browser

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

HTTPRequest.ts (6970B)


      1 /**
      2 * @license
      3 * Copyright 2020 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 import type {Protocol} from 'devtools-protocol';
      7 
      8 import type {CDPSession} from '../api/CDPSession.js';
      9 import type {Frame} from '../api/Frame.js';
     10 import {
     11  type ContinueRequestOverrides,
     12  headersArray,
     13  HTTPRequest,
     14  type ResourceType,
     15  type ResponseForRequest,
     16  STATUS_TEXTS,
     17  handleError,
     18 } from '../api/HTTPRequest.js';
     19 import {debugError} from '../common/util.js';
     20 import {stringToBase64} from '../util/encoding.js';
     21 
     22 import type {CdpHTTPResponse} from './HTTPResponse.js';
     23 
     24 /**
     25 * @internal
     26 */
     27 export class CdpHTTPRequest extends HTTPRequest {
     28  override id: string;
     29  declare _redirectChain: CdpHTTPRequest[];
     30  declare _response: CdpHTTPResponse | null;
     31 
     32  #client: CDPSession;
     33  #isNavigationRequest: boolean;
     34 
     35  #url: string;
     36  #resourceType: ResourceType;
     37 
     38  #method: string;
     39  #hasPostData = false;
     40  #postData?: string;
     41  #headers: Record<string, string> = {};
     42  #frame: Frame | null;
     43  #initiator?: Protocol.Network.Initiator;
     44 
     45  override get client(): CDPSession {
     46    return this.#client;
     47  }
     48 
     49  override set client(newClient: CDPSession) {
     50    this.#client = newClient;
     51  }
     52 
     53  constructor(
     54    client: CDPSession,
     55    frame: Frame | null,
     56    interceptionId: string | undefined,
     57    allowInterception: boolean,
     58    data: {
     59      /**
     60       * Request identifier.
     61       */
     62      requestId: Protocol.Network.RequestId;
     63      /**
     64       * Loader identifier. Empty string if the request is fetched from worker.
     65       */
     66      loaderId?: Protocol.Network.LoaderId;
     67      /**
     68       * URL of the document this request is loaded for.
     69       */
     70      documentURL?: string;
     71      /**
     72       * Request data.
     73       */
     74      request: Protocol.Network.Request;
     75      /**
     76       * Request initiator.
     77       */
     78      initiator?: Protocol.Network.Initiator;
     79      /**
     80       * Type of this resource.
     81       */
     82      type?: Protocol.Network.ResourceType;
     83    },
     84    redirectChain: CdpHTTPRequest[],
     85  ) {
     86    super();
     87    this.#client = client;
     88    this.id = data.requestId;
     89    this.#isNavigationRequest =
     90      data.requestId === data.loaderId && data.type === 'Document';
     91    this._interceptionId = interceptionId;
     92    this.#url = data.request.url + (data.request.urlFragment ?? '');
     93    this.#resourceType = (data.type || 'other').toLowerCase() as ResourceType;
     94    this.#method = data.request.method;
     95    this.#postData = data.request.postData;
     96    this.#hasPostData = data.request.hasPostData ?? false;
     97    this.#frame = frame;
     98    this._redirectChain = redirectChain;
     99    this.#initiator = data.initiator;
    100 
    101    this.interception.enabled = allowInterception;
    102 
    103    for (const [key, value] of Object.entries(data.request.headers)) {
    104      this.#headers[key.toLowerCase()] = value;
    105    }
    106  }
    107 
    108  override url(): string {
    109    return this.#url;
    110  }
    111 
    112  override resourceType(): ResourceType {
    113    return this.#resourceType;
    114  }
    115 
    116  override method(): string {
    117    return this.#method;
    118  }
    119 
    120  override postData(): string | undefined {
    121    return this.#postData;
    122  }
    123 
    124  override hasPostData(): boolean {
    125    return this.#hasPostData;
    126  }
    127 
    128  override async fetchPostData(): Promise<string | undefined> {
    129    try {
    130      const result = await this.#client.send('Network.getRequestPostData', {
    131        requestId: this.id,
    132      });
    133      return result.postData;
    134    } catch (err) {
    135      debugError(err);
    136      return;
    137    }
    138  }
    139 
    140  override headers(): Record<string, string> {
    141    return this.#headers;
    142  }
    143 
    144  override response(): CdpHTTPResponse | null {
    145    return this._response;
    146  }
    147 
    148  override frame(): Frame | null {
    149    return this.#frame;
    150  }
    151 
    152  override isNavigationRequest(): boolean {
    153    return this.#isNavigationRequest;
    154  }
    155 
    156  override initiator(): Protocol.Network.Initiator | undefined {
    157    return this.#initiator;
    158  }
    159 
    160  override redirectChain(): CdpHTTPRequest[] {
    161    return this._redirectChain.slice();
    162  }
    163 
    164  override failure(): {errorText: string} | null {
    165    if (!this._failureText) {
    166      return null;
    167    }
    168    return {
    169      errorText: this._failureText,
    170    };
    171  }
    172 
    173  /**
    174   * @internal
    175   */
    176  async _continue(overrides: ContinueRequestOverrides = {}): Promise<void> {
    177    const {url, method, postData, headers} = overrides;
    178    this.interception.handled = true;
    179 
    180    const postDataBinaryBase64 = postData
    181      ? stringToBase64(postData)
    182      : undefined;
    183 
    184    if (this._interceptionId === undefined) {
    185      throw new Error(
    186        'HTTPRequest is missing _interceptionId needed for Fetch.continueRequest',
    187      );
    188    }
    189    await this.#client
    190      .send('Fetch.continueRequest', {
    191        requestId: this._interceptionId,
    192        url,
    193        method,
    194        postData: postDataBinaryBase64,
    195        headers: headers ? headersArray(headers) : undefined,
    196      })
    197      .catch(error => {
    198        this.interception.handled = false;
    199        return handleError(error);
    200      });
    201  }
    202 
    203  async _respond(response: Partial<ResponseForRequest>): Promise<void> {
    204    this.interception.handled = true;
    205 
    206    let parsedBody:
    207      | {
    208          contentLength: number;
    209          base64: string;
    210        }
    211      | undefined;
    212    if (response.body) {
    213      parsedBody = HTTPRequest.getResponse(response.body);
    214    }
    215 
    216    const responseHeaders: Record<string, string | string[]> = {};
    217    if (response.headers) {
    218      for (const header of Object.keys(response.headers)) {
    219        const value = response.headers[header];
    220 
    221        responseHeaders[header.toLowerCase()] = Array.isArray(value)
    222          ? value.map(item => {
    223              return String(item);
    224            })
    225          : String(value);
    226      }
    227    }
    228    if (response.contentType) {
    229      responseHeaders['content-type'] = response.contentType;
    230    }
    231    if (parsedBody?.contentLength && !('content-length' in responseHeaders)) {
    232      responseHeaders['content-length'] = String(parsedBody.contentLength);
    233    }
    234 
    235    const status = response.status || 200;
    236    if (this._interceptionId === undefined) {
    237      throw new Error(
    238        'HTTPRequest is missing _interceptionId needed for Fetch.fulfillRequest',
    239      );
    240    }
    241    await this.#client
    242      .send('Fetch.fulfillRequest', {
    243        requestId: this._interceptionId,
    244        responseCode: status,
    245        responsePhrase: STATUS_TEXTS[status],
    246        responseHeaders: headersArray(responseHeaders),
    247        body: parsedBody?.base64,
    248      })
    249      .catch(error => {
    250        this.interception.handled = false;
    251        return handleError(error);
    252      });
    253  }
    254 
    255  async _abort(
    256    errorReason: Protocol.Network.ErrorReason | null,
    257  ): Promise<void> {
    258    this.interception.handled = true;
    259    if (this._interceptionId === undefined) {
    260      throw new Error(
    261        'HTTPRequest is missing _interceptionId needed for Fetch.failRequest',
    262      );
    263    }
    264    await this.#client
    265      .send('Fetch.failRequest', {
    266        requestId: this._interceptionId,
    267        errorReason: errorReason || 'Failed',
    268      })
    269      .catch(handleError);
    270  }
    271 }