tor-browser

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

HTTPResponse.ts (4711B)


      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 {Frame} from '../api/Frame.js';
      9 import {HTTPResponse, type RemoteAddress} from '../api/HTTPResponse.js';
     10 import {ProtocolError} from '../common/Errors.js';
     11 import {SecurityDetails} from '../common/SecurityDetails.js';
     12 import {Deferred} from '../util/Deferred.js';
     13 import {stringToTypedArray} from '../util/encoding.js';
     14 
     15 import type {CdpHTTPRequest} from './HTTPRequest.js';
     16 
     17 /**
     18 * @internal
     19 */
     20 export class CdpHTTPResponse extends HTTPResponse {
     21  #request: CdpHTTPRequest;
     22  #contentPromise: Promise<Uint8Array> | null = null;
     23  #bodyLoadedDeferred = Deferred.create<void, Error>();
     24  #remoteAddress: RemoteAddress;
     25  #status: number;
     26  #statusText: string;
     27  #fromDiskCache: boolean;
     28  #fromServiceWorker: boolean;
     29  #headers: Record<string, string> = {};
     30  #securityDetails: SecurityDetails | null;
     31  #timing: Protocol.Network.ResourceTiming | null;
     32 
     33  constructor(
     34    request: CdpHTTPRequest,
     35    responsePayload: Protocol.Network.Response,
     36    extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null,
     37  ) {
     38    super();
     39    this.#request = request;
     40 
     41    this.#remoteAddress = {
     42      ip: responsePayload.remoteIPAddress,
     43      port: responsePayload.remotePort,
     44    };
     45    this.#statusText =
     46      this.#parseStatusTextFromExtraInfo(extraInfo) ||
     47      responsePayload.statusText;
     48    this.#fromDiskCache = !!responsePayload.fromDiskCache;
     49    this.#fromServiceWorker = !!responsePayload.fromServiceWorker;
     50 
     51    this.#status = extraInfo ? extraInfo.statusCode : responsePayload.status;
     52    const headers = extraInfo ? extraInfo.headers : responsePayload.headers;
     53    for (const [key, value] of Object.entries(headers)) {
     54      this.#headers[key.toLowerCase()] = value;
     55    }
     56 
     57    this.#securityDetails = responsePayload.securityDetails
     58      ? new SecurityDetails(responsePayload.securityDetails)
     59      : null;
     60    this.#timing = responsePayload.timing || null;
     61  }
     62 
     63  #parseStatusTextFromExtraInfo(
     64    extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null,
     65  ): string | undefined {
     66    if (!extraInfo || !extraInfo.headersText) {
     67      return;
     68    }
     69    const firstLine = extraInfo.headersText.split('\r', 1)[0];
     70    if (!firstLine || firstLine.length > 1_000) {
     71      return;
     72    }
     73    const match = firstLine.match(/[^ ]* [^ ]* (.*)/);
     74    if (!match) {
     75      return;
     76    }
     77    const statusText = match[1];
     78    if (!statusText) {
     79      return;
     80    }
     81    return statusText;
     82  }
     83 
     84  _resolveBody(err?: Error): void {
     85    if (err) {
     86      return this.#bodyLoadedDeferred.reject(err);
     87    }
     88    return this.#bodyLoadedDeferred.resolve();
     89  }
     90 
     91  override remoteAddress(): RemoteAddress {
     92    return this.#remoteAddress;
     93  }
     94 
     95  override url(): string {
     96    return this.#request.url();
     97  }
     98 
     99  override status(): number {
    100    return this.#status;
    101  }
    102 
    103  override statusText(): string {
    104    return this.#statusText;
    105  }
    106 
    107  override headers(): Record<string, string> {
    108    return this.#headers;
    109  }
    110 
    111  override securityDetails(): SecurityDetails | null {
    112    return this.#securityDetails;
    113  }
    114 
    115  override timing(): Protocol.Network.ResourceTiming | null {
    116    return this.#timing;
    117  }
    118 
    119  override content(): Promise<Uint8Array> {
    120    if (!this.#contentPromise) {
    121      this.#contentPromise = this.#bodyLoadedDeferred
    122        .valueOrThrow()
    123        .then(async () => {
    124          try {
    125            // Use CDPSession from corresponding request to retrieve body, as it's client
    126            // might have been updated (e.g. for an adopted OOPIF).
    127            const response = await this.#request.client.send(
    128              'Network.getResponseBody',
    129              {
    130                requestId: this.#request.id,
    131              },
    132            );
    133 
    134            return stringToTypedArray(response.body, response.base64Encoded);
    135          } catch (error) {
    136            if (
    137              error instanceof ProtocolError &&
    138              error.originalMessage ===
    139                'No resource with given identifier found'
    140            ) {
    141              throw new ProtocolError(
    142                'Could not load body for this request. This might happen if the request is a preflight request.',
    143              );
    144            }
    145 
    146            throw error;
    147          }
    148        });
    149    }
    150    return this.#contentPromise;
    151  }
    152 
    153  override request(): CdpHTTPRequest {
    154    return this.#request;
    155  }
    156 
    157  override fromCache(): boolean {
    158    return this.#fromDiskCache || this.#request._fromMemoryCache;
    159  }
    160 
    161  override fromServiceWorker(): boolean {
    162    return this.#fromServiceWorker;
    163  }
    164 
    165  override frame(): Frame | null {
    166    return this.#request.frame();
    167  }
    168 }