tor-browser

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

RequestListContextMenu.js (26490B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  L10N,
      9 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js");
     10 const {
     11  formDataURI,
     12  getUrlQuery,
     13  getUrlBaseName,
     14  parseQueryString,
     15  getRequestHeadersRawText,
     16 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
     17 const {
     18  hasMatchingBlockingRequestPattern,
     19 } = require("resource://devtools/client/netmonitor/src/utils/request-blocking.js");
     20 
     21 loader.lazyRequireGetter(
     22  this,
     23  "Curl",
     24  "resource://devtools/client/shared/curl.js",
     25  true
     26 );
     27 loader.lazyRequireGetter(
     28  this,
     29  "saveAs",
     30  "resource://devtools/shared/DevToolsUtils.js",
     31  true
     32 );
     33 loader.lazyRequireGetter(
     34  this,
     35  "PowerShell",
     36  "resource://devtools/client/netmonitor/src/utils/powershell.js",
     37  true
     38 );
     39 loader.lazyRequireGetter(
     40  this,
     41  "copyString",
     42  "resource://devtools/shared/platform/clipboard.js",
     43  true
     44 );
     45 loader.lazyRequireGetter(
     46  this,
     47  "showMenu",
     48  "resource://devtools/client/shared/components/menu/utils.js",
     49  true
     50 );
     51 loader.lazyRequireGetter(
     52  this,
     53  "HarMenuUtils",
     54  "resource://devtools/client/netmonitor/src/har/har-menu-utils.js",
     55  true
     56 );
     57 loader.lazyRequireGetter(
     58  this,
     59  ["setNetworkOverride", "removeNetworkOverride"],
     60  "resource://devtools/client/framework/actions/index.js",
     61  true
     62 );
     63 loader.lazyRequireGetter(
     64  this,
     65  "getOverriddenUrl",
     66  "resource://devtools/client/netmonitor/src/selectors/index.js",
     67  true
     68 );
     69 loader.lazyRequireGetter(
     70  this,
     71  "openRequestInTab",
     72  "resource://devtools/client/netmonitor/src/utils/firefox/open-request-in-tab.js",
     73  true
     74 );
     75 
     76 const { OS } = Services.appinfo;
     77 
     78 class RequestListContextMenu {
     79  constructor(props) {
     80    this.props = props;
     81  }
     82 
     83  createCopySubMenu(clickedRequest, requests) {
     84    const { connector } = this.props;
     85 
     86    const {
     87      id,
     88      formDataSections,
     89      method,
     90      mimeType,
     91      httpVersion,
     92      requestHeaders,
     93      requestHeadersAvailable,
     94      requestPostData,
     95      requestPostDataAvailable,
     96      responseHeaders,
     97      responseHeadersAvailable,
     98      responseContent,
     99      responseContentAvailable,
    100      url,
    101    } = clickedRequest;
    102 
    103    const copySubMenu = [];
    104 
    105    copySubMenu.push({
    106      id: "request-list-context-copy-url",
    107      label: L10N.getStr("netmonitor.context.copyUrl"),
    108      accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"),
    109      visible: !!clickedRequest,
    110      click: () => this.copyUrl(url),
    111    });
    112 
    113    copySubMenu.push({
    114      id: "request-list-context-copy-url-params",
    115      label: L10N.getStr("netmonitor.context.copyUrlParams"),
    116      accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"),
    117      visible: !!(clickedRequest && getUrlQuery(url)),
    118      click: () => this.copyUrlParams(url),
    119    });
    120 
    121    copySubMenu.push({
    122      id: "request-list-context-copy-post-data",
    123      label: L10N.getFormatStr("netmonitor.context.copyRequestData", method),
    124      accesskey: L10N.getStr("netmonitor.context.copyRequestData.accesskey"),
    125      // Menu item will be visible even if data hasn't arrived, so we need to check
    126      // *Available property and then fetch data lazily once user triggers the action.
    127      visible: !!(
    128        clickedRequest &&
    129        (requestPostDataAvailable || requestPostData)
    130      ),
    131      click: () => this.copyPostData(id, formDataSections, requestPostData),
    132    });
    133 
    134    if (OS === "WINNT") {
    135      copySubMenu.push({
    136        id: "request-list-context-copy-as-curl-win",
    137        label: L10N.getFormatStr(
    138          "netmonitor.context.copyAsCurl.win",
    139          L10N.getStr("netmonitor.context.copyAsCurl")
    140        ),
    141        accesskey: L10N.getStr("netmonitor.context.copyAsCurl.win.accesskey"),
    142        // Menu item will be visible even if data hasn't arrived, so we need to check
    143        // *Available property and then fetch data lazily once user triggers the action.
    144        visible: !!clickedRequest,
    145        click: () =>
    146          this.copyAsCurl(
    147            id,
    148            url,
    149            method,
    150            httpVersion,
    151            requestHeaders,
    152            requestPostData,
    153            responseHeaders,
    154            "WINNT"
    155          ),
    156      });
    157 
    158      copySubMenu.push({
    159        id: "request-list-context-copy-as-curl-posix",
    160        label: L10N.getFormatStr(
    161          "netmonitor.context.copyAsCurl.posix",
    162          L10N.getStr("netmonitor.context.copyAsCurl")
    163        ),
    164        accesskey: L10N.getStr("netmonitor.context.copyAsCurl.posix.accesskey"),
    165        // Menu item will be visible even if data hasn't arrived, so we need to check
    166        // *Available property and then fetch data lazily once user triggers the action.
    167        visible: !!clickedRequest,
    168        click: () =>
    169          this.copyAsCurl(
    170            id,
    171            url,
    172            method,
    173            httpVersion,
    174            requestHeaders,
    175            requestPostData,
    176            responseHeaders,
    177            "Linux"
    178          ),
    179      });
    180    } else {
    181      copySubMenu.push({
    182        id: "request-list-context-copy-as-curl",
    183        label: L10N.getStr("netmonitor.context.copyAsCurl"),
    184        accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
    185        // Menu item will be visible even if data hasn't arrived, so we need to check
    186        // *Available property and then fetch data lazily once user triggers the action.
    187        visible: !!clickedRequest,
    188        click: () =>
    189          this.copyAsCurl(
    190            id,
    191            url,
    192            method,
    193            httpVersion,
    194            requestHeaders,
    195            requestPostData,
    196            responseHeaders
    197          ),
    198      });
    199    }
    200 
    201    copySubMenu.push({
    202      id: "request-list-context-copy-as-powershell",
    203      label: L10N.getStr("netmonitor.context.copyAsPowerShell"),
    204      accesskey: L10N.getStr("netmonitor.context.copyAsPowerShell.accesskey"),
    205      // Menu item will be visible even if data hasn't arrived, so we need to check
    206      // *Available property and then fetch data lazily once user triggers the action.
    207      visible: !!clickedRequest,
    208      click: () => this.copyAsPowerShell(clickedRequest),
    209    });
    210 
    211    copySubMenu.push({
    212      id: "request-list-context-copy-as-fetch",
    213      label: L10N.getStr("netmonitor.context.copyAsFetch"),
    214      accesskey: L10N.getStr("netmonitor.context.copyAsFetch.accesskey"),
    215      visible: !!clickedRequest,
    216      click: () =>
    217        this.copyAsFetch(id, url, method, requestHeaders, requestPostData),
    218    });
    219 
    220    copySubMenu.push({
    221      type: "separator",
    222      visible: copySubMenu.slice(0, 4).some(subMenu => subMenu.visible),
    223    });
    224 
    225    copySubMenu.push({
    226      id: "request-list-context-copy-request-headers",
    227      label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
    228      accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
    229      // Menu item will be visible even if data hasn't arrived, so we need to check
    230      // *Available property and then fetch data lazily once user triggers the action.
    231      visible: !!(
    232        clickedRequest &&
    233        (requestHeadersAvailable || requestHeaders)
    234      ),
    235      click: () => this.copyRequestHeaders(id, clickedRequest),
    236    });
    237 
    238    copySubMenu.push({
    239      id: "response-list-context-copy-response-headers",
    240      label: L10N.getStr("netmonitor.context.copyResponseHeaders"),
    241      accesskey: L10N.getStr(
    242        "netmonitor.context.copyResponseHeaders.accesskey"
    243      ),
    244      // Menu item will be visible even if data hasn't arrived, so we need to check
    245      // *Available property and then fetch data lazily once user triggers the action.
    246      visible: !!(
    247        clickedRequest &&
    248        (responseHeadersAvailable || responseHeaders)
    249      ),
    250      click: () => this.copyResponseHeaders(id, responseHeaders),
    251    });
    252 
    253    copySubMenu.push({
    254      id: "request-list-context-copy-response",
    255      label: L10N.getStr("netmonitor.context.copyResponse"),
    256      accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
    257      // Menu item will be visible even if data hasn't arrived, so we need to check
    258      // *Available property and then fetch data lazily once user triggers the action.
    259      visible: !!(
    260        clickedRequest &&
    261        (responseContentAvailable || responseContent)
    262      ),
    263      click: () => this.copyResponse(id, responseContent),
    264    });
    265 
    266    copySubMenu.push({
    267      id: "request-list-context-copy-image-as-data-uri",
    268      label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
    269      accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
    270      visible: !!(
    271        clickedRequest &&
    272        (responseContentAvailable || responseContent) &&
    273        mimeType &&
    274        mimeType.includes("image/")
    275      ),
    276      click: () => this.copyImageAsDataUri(id, mimeType, responseContent),
    277    });
    278 
    279    copySubMenu.push({
    280      type: "separator",
    281      visible: copySubMenu.slice(5, 9).some(subMenu => subMenu.visible),
    282    });
    283 
    284    copySubMenu.push({
    285      id: "request-list-context-copy-all-as-har",
    286      label: L10N.getStr("netmonitor.context.copyAllAsHar"),
    287      accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"),
    288      visible: !!requests.length,
    289      click: () => HarMenuUtils.copyAllAsHar(requests, connector),
    290    });
    291 
    292    return copySubMenu;
    293  }
    294 
    295  createMenu(clickedRequest, requests, blockedUrls) {
    296    const {
    297      connector,
    298      cloneRequest,
    299      openDetailsPanelTab,
    300      openHTTPCustomRequestTab,
    301      closeHTTPCustomRequestTab,
    302      sendCustomRequest,
    303      sendHTTPCustomRequest,
    304      openStatistics,
    305      openRequestBlockingAndAddUrl,
    306      openRequestBlockingAndDisableUrls,
    307      removeBlockedUrl,
    308    } = this.props;
    309 
    310    const {
    311      id,
    312      isCustom,
    313      method,
    314      cause,
    315      isEventStream,
    316      mimeType,
    317      requestHeaders,
    318      requestPostData,
    319      responseContent,
    320      responseContentAvailable,
    321      url,
    322    } = clickedRequest;
    323 
    324    const toolbox = this.props.connector.getToolbox();
    325    const isOverridden = !!getOverriddenUrl(toolbox.store.getState(), url);
    326    const isLocalTab = toolbox.commands.descriptorFront.isLocalTab;
    327 
    328    const copySubMenu = this.createCopySubMenu(clickedRequest, requests);
    329    const newEditAndResendPref = Services.prefs.getBoolPref(
    330      "devtools.netmonitor.features.newEditAndResend"
    331    );
    332 
    333    return [
    334      {
    335        label: L10N.getStr("netmonitor.context.copyValue"),
    336        accesskey: L10N.getStr("netmonitor.context.copyValue.accesskey"),
    337        visible: true,
    338        submenu: copySubMenu,
    339      },
    340      {
    341        id: "request-list-context-save-as-har",
    342        label: L10N.getStr("netmonitor.context.saveAsHar"),
    343        accesskey: L10N.getStr("netmonitor.context.saveAsHar.accesskey"),
    344        visible: !!clickedRequest,
    345        click: () => HarMenuUtils.saveAsHar(clickedRequest, connector),
    346      },
    347      {
    348        id: "request-list-context-save-all-as-har",
    349        label: L10N.getStr("netmonitor.context.saveAllAsHar"),
    350        accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
    351        visible: !!requests.length,
    352        click: () => HarMenuUtils.saveAllAsHar(requests, connector),
    353      },
    354      {
    355        id: "request-list-context-save-response-as",
    356        label: L10N.getStr("netmonitor.context.saveResponseAs"),
    357        accesskey: L10N.getStr("netmonitor.context.saveResponseAs.accesskey"),
    358        visible: !!(
    359          (responseContentAvailable || responseContent) &&
    360          mimeType &&
    361          // Websockets and server-sent events don't have a real 'response' for us to save
    362          cause.type !== "websocket" &&
    363          !isEventStream
    364        ),
    365        click: () => this.saveResponseAs(id, url, responseContent),
    366      },
    367      {
    368        type: "separator",
    369        visible: copySubMenu.slice(10, 14).some(subMenu => subMenu.visible),
    370      },
    371      {
    372        id: "request-list-context-resend-only",
    373        label: L10N.getStr("netmonitor.context.resend.label"),
    374        accesskey: L10N.getStr("netmonitor.context.resend.accesskey"),
    375        visible: !isCustom,
    376        click: () => {
    377          if (!newEditAndResendPref) {
    378            cloneRequest(id);
    379            sendCustomRequest();
    380          } else {
    381            sendHTTPCustomRequest(clickedRequest);
    382          }
    383        },
    384      },
    385 
    386      {
    387        id: "request-list-context-edit-resend",
    388        label: L10N.getStr("netmonitor.context.editAndResend"),
    389        accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
    390        visible: !isCustom,
    391        click: () => {
    392          this.fetchRequestHeaders(id).then(() => {
    393            if (!newEditAndResendPref) {
    394              cloneRequest(id);
    395              openDetailsPanelTab();
    396            } else {
    397              closeHTTPCustomRequestTab();
    398              openHTTPCustomRequestTab();
    399            }
    400          });
    401        },
    402      },
    403      // Request blocking
    404      {
    405        id: "request-list-context-block-url",
    406        label: L10N.getStr("netmonitor.context.blockURL"),
    407        visible: !hasMatchingBlockingRequestPattern(
    408          blockedUrls,
    409          clickedRequest.url
    410        ),
    411        click: () => {
    412          openRequestBlockingAndAddUrl(clickedRequest.url);
    413        },
    414      },
    415      {
    416        id: "request-list-context-unblock-url",
    417        label: L10N.getStr("netmonitor.context.unblockURL"),
    418        visible: hasMatchingBlockingRequestPattern(
    419          blockedUrls,
    420          clickedRequest.url
    421        ),
    422        click: () => {
    423          if (
    424            blockedUrls.find(blockedUrl => blockedUrl === clickedRequest.url)
    425          ) {
    426            removeBlockedUrl(clickedRequest.url);
    427          } else {
    428            openRequestBlockingAndDisableUrls(clickedRequest.url);
    429          }
    430        },
    431      },
    432      // Request overrides
    433      {
    434        id: "request-list-context-set-override",
    435        label: L10N.getStr("netmonitor.context.setOverride"),
    436        accesskey: L10N.getStr("netmonitor.context.setOverride.accesskey"),
    437        visible:
    438          // Network overrides are disabled for remote debugging (bug 1881441).
    439          isLocalTab &&
    440          !isOverridden &&
    441          (responseContentAvailable || responseContent),
    442        click: async () => {
    443          const content = await this.getResponseContent(id, responseContent);
    444          toolbox.store.dispatch(
    445            setNetworkOverride(toolbox.commands, url, content, window)
    446          );
    447        },
    448      },
    449      {
    450        id: "request-list-context-remove-override",
    451        label: L10N.getStr("netmonitor.context.removeOverride"),
    452        accesskey: L10N.getStr("netmonitor.context.removeOverride.accesskey"),
    453        // Network overrides are disabled for remote debugging (bug 1881441).
    454        visible: isLocalTab && isOverridden,
    455        click: () =>
    456          toolbox.store.dispatch(removeNetworkOverride(toolbox.commands, url)),
    457      },
    458      {
    459        type: "separator",
    460        visible: copySubMenu.slice(15, 16).some(subMenu => subMenu.visible),
    461      },
    462      {
    463        id: "request-list-context-newtab",
    464        label: L10N.getStr("netmonitor.context.newTab"),
    465        accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
    466        visible: !!clickedRequest,
    467        click: () =>
    468          this.openRequestInTab(id, url, requestHeaders, requestPostData),
    469      },
    470      {
    471        id: "request-list-context-open-in-debugger",
    472        label: L10N.getStr("netmonitor.context.openInDebugger"),
    473        accesskey: L10N.getStr("netmonitor.context.openInDebugger.accesskey"),
    474        visible: !!(
    475          clickedRequest &&
    476          mimeType &&
    477          mimeType.includes("javascript")
    478        ),
    479        click: () => this.openInDebugger(url),
    480      },
    481      {
    482        id: "request-list-context-open-in-style-editor",
    483        label: L10N.getStr("netmonitor.context.openInStyleEditor"),
    484        accesskey: L10N.getStr(
    485          "netmonitor.context.openInStyleEditor.accesskey"
    486        ),
    487        visible: !!(
    488          clickedRequest &&
    489          Services.prefs.getBoolPref("devtools.styleeditor.enabled") &&
    490          mimeType &&
    491          mimeType.includes("css")
    492        ),
    493        click: () => this.openInStyleEditor(url),
    494      },
    495      {
    496        id: "request-list-context-perf",
    497        label: L10N.getStr("netmonitor.context.perfTools"),
    498        accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"),
    499        visible: !!requests.length,
    500        click: () => openStatistics(true),
    501      },
    502      {
    503        type: "separator",
    504      },
    505      {
    506        id: "request-list-context-use-as-fetch",
    507        label: L10N.getStr("netmonitor.context.useAsFetch"),
    508        accesskey: L10N.getStr("netmonitor.context.useAsFetch.accesskey"),
    509        visible: !!clickedRequest,
    510        click: () =>
    511          this.useAsFetch(id, url, method, requestHeaders, requestPostData),
    512      },
    513    ];
    514  }
    515 
    516  /**
    517   * Opens selected item in a new tab.
    518   */
    519  async openRequestInTab(id, url, requestHeaders, requestPostData) {
    520    requestHeaders =
    521      requestHeaders ||
    522      (await this.props.connector.requestData(id, "requestHeaders"));
    523 
    524    requestPostData =
    525      requestPostData ||
    526      (await this.props.connector.requestData(id, "requestPostData"));
    527 
    528    openRequestInTab(url, requestHeaders, requestPostData);
    529  }
    530 
    531  open(event, clickedRequest, displayedRequests, blockedUrls) {
    532    const enabledBlockedUrls = blockedUrls
    533      .map(({ enabled, url }) => (enabled ? url : null))
    534      .filter(Boolean);
    535 
    536    const menu = this.createMenu(
    537      clickedRequest,
    538      displayedRequests,
    539      enabledBlockedUrls
    540    );
    541 
    542    showMenu(menu, {
    543      screenX: event.screenX,
    544      screenY: event.screenY,
    545    });
    546  }
    547 
    548  /**
    549   * Opens selected item in the debugger
    550   */
    551  openInDebugger(url) {
    552    const toolbox = this.props.connector.getToolbox();
    553    toolbox.viewGeneratedSourceInDebugger(url);
    554  }
    555 
    556  /**
    557   * Opens selected item in the style editor
    558   */
    559  openInStyleEditor(url) {
    560    const toolbox = this.props.connector.getToolbox();
    561    toolbox.viewGeneratedSourceInStyleEditor(url);
    562  }
    563 
    564  /**
    565   * Copy the request url from the currently selected item.
    566   */
    567  copyUrl(url) {
    568    copyString(url);
    569  }
    570 
    571  /**
    572   * Copy the request url query string parameters from the currently
    573   * selected item.
    574   */
    575  copyUrlParams(url) {
    576    const params = getUrlQuery(url).split("&");
    577    copyString(params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n"));
    578  }
    579 
    580  /**
    581   * Copy the request form data parameters (or raw payload) from
    582   * the currently selected item.
    583   */
    584  async copyPostData(id, formDataSections, requestPostData) {
    585    let params = [];
    586    // Try to extract any form data parameters if formDataSections is already
    587    // available, which is only true if RequestPanel has ever been mounted before.
    588    if (formDataSections) {
    589      formDataSections.forEach(section => {
    590        const paramsArray = parseQueryString(section);
    591        if (paramsArray) {
    592          params = [...params, ...paramsArray];
    593        }
    594      });
    595    }
    596 
    597    let string = params
    598      .map(param => param.name + (param.value ? "=" + param.value : ""))
    599      .join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
    600 
    601    // Fall back to raw payload.
    602    if (!string) {
    603      requestPostData =
    604        requestPostData ||
    605        (await this.props.connector.requestData(id, "requestPostData"));
    606 
    607      string = requestPostData.postData.text;
    608      if (Services.appinfo.OS !== "WINNT") {
    609        string = string.replace(/\r/g, "");
    610      }
    611    }
    612    copyString(string);
    613  }
    614 
    615  /**
    616   * Copy a cURL command from the currently selected item.
    617   */
    618  async copyAsCurl(
    619    id,
    620    url,
    621    method,
    622    httpVersion,
    623    requestHeaders,
    624    requestPostData,
    625    responseHeaders,
    626    platform
    627  ) {
    628    requestHeaders =
    629      requestHeaders ||
    630      (await this.props.connector.requestData(id, "requestHeaders"));
    631 
    632    requestPostData =
    633      requestPostData ||
    634      (await this.props.connector.requestData(id, "requestPostData"));
    635 
    636    responseHeaders =
    637      responseHeaders ||
    638      (await this.props.connector.requestData(id, "responseHeaders"));
    639 
    640    // Create a sanitized object for the Curl command generator.
    641    const data = {
    642      url,
    643      method,
    644      headers: requestHeaders.headers,
    645      responseHeaders: responseHeaders.headers,
    646      httpVersion,
    647      postDataText: requestPostData ? requestPostData.postData.text : "",
    648    };
    649    copyString(Curl.generateCommand(data, platform));
    650  }
    651 
    652  async copyAsPowerShell(request) {
    653    let { id, url, method, requestHeaders, requestPostData, requestCookies } =
    654      request;
    655 
    656    requestHeaders =
    657      requestHeaders ||
    658      (await this.props.connector.requestData(id, "requestHeaders"));
    659 
    660    requestPostData =
    661      requestPostData ||
    662      (await this.props.connector.requestData(id, "requestPostData"));
    663 
    664    requestCookies =
    665      requestCookies ||
    666      (await this.props.connector.requestData(id, "requestCookies"));
    667 
    668    return copyString(
    669      PowerShell.generateCommand(
    670        url,
    671        method,
    672        requestHeaders.headers,
    673        requestPostData.postData,
    674        requestCookies.cookies || requestCookies
    675      )
    676    );
    677  }
    678 
    679  /**
    680   * Generate fetch string
    681   */
    682  async generateFetchString(id, url, method, requestHeaders, requestPostData) {
    683    requestHeaders =
    684      requestHeaders ||
    685      (await this.props.connector.requestData(id, "requestHeaders"));
    686 
    687    requestPostData =
    688      requestPostData ||
    689      (await this.props.connector.requestData(id, "requestPostData"));
    690 
    691    // https://fetch.spec.whatwg.org/#forbidden-header-name
    692    const forbiddenHeaders = {
    693      "accept-charset": 1,
    694      "accept-encoding": 1,
    695      "access-control-request-headers": 1,
    696      "access-control-request-method": 1,
    697      connection: 1,
    698      "content-length": 1,
    699      cookie: 1,
    700      cookie2: 1,
    701      date: 1,
    702      dnt: 1,
    703      expect: 1,
    704      host: 1,
    705      "keep-alive": 1,
    706      origin: 1,
    707      referer: 1,
    708      te: 1,
    709      trailer: 1,
    710      "transfer-encoding": 1,
    711      upgrade: 1,
    712      via: 1,
    713    };
    714    const credentialHeaders = { cookie: 1, authorization: 1 };
    715 
    716    const headers = {};
    717    for (const { name, value } of requestHeaders.headers) {
    718      if (!forbiddenHeaders[name.toLowerCase()]) {
    719        headers[name] = value;
    720      }
    721    }
    722 
    723    const referrerHeader = requestHeaders.headers.find(
    724      ({ name }) => name.toLowerCase() === "referer"
    725    );
    726 
    727    const referrerPolicy = requestHeaders.headers.find(
    728      ({ name }) => name.toLowerCase() === "referrer-policy"
    729    );
    730 
    731    const referrer = referrerHeader ? referrerHeader.value : undefined;
    732    const credentials = requestHeaders.headers.some(
    733      ({ name }) => credentialHeaders[name.toLowerCase()]
    734    )
    735      ? "include"
    736      : "omit";
    737 
    738    const fetchOptions = {
    739      credentials,
    740      headers,
    741      referrer,
    742      referrerPolicy,
    743      body: requestPostData.postData.text,
    744      method,
    745      mode: "cors",
    746    };
    747 
    748    const options = JSON.stringify(fetchOptions, null, 4);
    749    const fetchString = `await fetch("${url}", ${options});`;
    750    return fetchString;
    751  }
    752 
    753  /**
    754   * Copy the currently selected item as fetch request.
    755   */
    756  async copyAsFetch(id, url, method, requestHeaders, requestPostData) {
    757    const fetchString = await this.generateFetchString(
    758      id,
    759      url,
    760      method,
    761      requestHeaders,
    762      requestPostData
    763    );
    764    copyString(fetchString);
    765  }
    766 
    767  /**
    768   * Open split console and fill it with fetch command for selected item
    769   */
    770  async useAsFetch(id, url, method, requestHeaders, requestPostData) {
    771    const fetchString = await this.generateFetchString(
    772      id,
    773      url,
    774      method,
    775      requestHeaders,
    776      requestPostData
    777    );
    778    const toolbox = this.props.connector.getToolbox();
    779    await toolbox.openSplitConsole();
    780    const { hud } = await toolbox.getPanel("webconsole");
    781    hud.setInputValue(fetchString);
    782  }
    783 
    784  /**
    785   * Copy the raw request headers from the currently selected item.
    786   */
    787  async copyRequestHeaders(
    788    id,
    789    { method, httpVersion, requestHeaders, urlDetails }
    790  ) {
    791    requestHeaders =
    792      requestHeaders ||
    793      (await this.props.connector.requestData(id, "requestHeaders"));
    794 
    795    let rawHeaders = getRequestHeadersRawText(
    796      method,
    797      httpVersion,
    798      requestHeaders,
    799      urlDetails
    800    );
    801 
    802    if (Services.appinfo.OS !== "WINNT") {
    803      rawHeaders = rawHeaders.replace(/\r/g, "");
    804    }
    805    copyString(rawHeaders);
    806  }
    807 
    808  /**
    809   * Copy the raw response headers from the currently selected item.
    810   */
    811  async copyResponseHeaders(id, responseHeaders) {
    812    responseHeaders =
    813      responseHeaders ||
    814      (await this.props.connector.requestData(id, "responseHeaders"));
    815 
    816    let rawHeaders = responseHeaders.rawHeaders.trim();
    817 
    818    if (Services.appinfo.OS !== "WINNT") {
    819      rawHeaders = rawHeaders.replace(/\r/g, "");
    820    }
    821    copyString(rawHeaders);
    822  }
    823 
    824  /**
    825   * Copy image as data uri.
    826   */
    827  async copyImageAsDataUri(id, mimeType, responseContent) {
    828    responseContent =
    829      responseContent ||
    830      (await this.props.connector.requestData(id, "responseContent"));
    831 
    832    const { encoding, text } = responseContent.content;
    833    copyString(formDataURI(mimeType, encoding, text));
    834  }
    835 
    836  async getResponseContent(id, responseContent) {
    837    responseContent =
    838      responseContent ||
    839      (await this.props.connector.requestData(id, "responseContent"));
    840 
    841    const { encoding, text } = responseContent.content;
    842    let data;
    843    if (encoding === "base64") {
    844      const decoded = atob(text);
    845      data = new Uint8Array(decoded.length);
    846      for (let i = 0; i < decoded.length; ++i) {
    847        data[i] = decoded.charCodeAt(i);
    848      }
    849    } else {
    850      data = new TextEncoder().encode(text);
    851    }
    852    return data;
    853  }
    854 
    855  /**
    856   * Save response as.
    857   */
    858  async saveResponseAs(id, url, responseContent) {
    859    const fileName = getUrlBaseName(url);
    860    const content = await this.getResponseContent(id, responseContent);
    861    saveAs(window, content, fileName);
    862  }
    863 
    864  /**
    865   * Copy response data as a string.
    866   */
    867  async copyResponse(id, responseContent) {
    868    responseContent =
    869      responseContent ||
    870      (await this.props.connector.requestData(id, "responseContent"));
    871 
    872    copyString(responseContent.content.text);
    873  }
    874 
    875  async fetchRequestHeaders(id) {
    876    await this.props.connector.requestData(id, "requestHeaders");
    877  }
    878 }
    879 
    880 module.exports = RequestListContextMenu;