tor-browser

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

CustomRequestPanel.js (11601B)


      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  Component,
      9 } = require("resource://devtools/client/shared/vendor/react.mjs");
     10 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     11 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     12 const {
     13  connect,
     14 } = require("resource://devtools/client/shared/vendor/react-redux.js");
     15 const {
     16  L10N,
     17 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js");
     18 const {
     19  fetchNetworkUpdatePacket,
     20 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
     21 const Actions = require("resource://devtools/client/netmonitor/src/actions/index.js");
     22 const {
     23  getSelectedRequest,
     24 } = require("resource://devtools/client/netmonitor/src/selectors/index.js");
     25 const {
     26  getUrlQuery,
     27  parseQueryString,
     28  writeHeaderText,
     29 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
     30 
     31 const { button, div, input, label, textarea } = dom;
     32 
     33 const CUSTOM_CANCEL = L10N.getStr("netmonitor.custom.cancel");
     34 const CUSTOM_HEADERS = L10N.getStr("netmonitor.custom.headers");
     35 const CUSTOM_NEW_REQUEST = L10N.getStr("netmonitor.custom.newRequest");
     36 const CUSTOM_NEW_REQUEST_METHOD_LABEL = L10N.getStr(
     37  "netmonitor.custom.newRequestMethodLabel"
     38 );
     39 const CUSTOM_NEW_REQUEST_URL_LABEL = L10N.getStr(
     40  "netmonitor.custom.newRequestUrlLabel"
     41 );
     42 const CUSTOM_POSTDATA = L10N.getStr("netmonitor.custom.postData");
     43 const CUSTOM_QUERY = L10N.getStr("netmonitor.custom.query");
     44 const CUSTOM_SEND = L10N.getStr("netmonitor.custom.send");
     45 
     46 /*
     47 * Custom request panel component
     48 * A network request editor which simply provide edit and resend interface
     49 * for network development.
     50 */
     51 class CustomRequestPanel extends Component {
     52  static get propTypes() {
     53    return {
     54      connector: PropTypes.object.isRequired,
     55      removeSelectedCustomRequest: PropTypes.func.isRequired,
     56      request: PropTypes.object.isRequired,
     57      sendCustomRequest: PropTypes.func.isRequired,
     58      updateRequest: PropTypes.func.isRequired,
     59    };
     60  }
     61 
     62  componentDidMount() {
     63    const { request, connector } = this.props;
     64    this.initialRequestMethod = request.method;
     65    fetchNetworkUpdatePacket(connector.requestData, request, [
     66      "requestHeaders",
     67      "responseHeaders",
     68      "requestPostData",
     69    ]);
     70  }
     71 
     72  // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507
     73  UNSAFE_componentWillReceiveProps(nextProps) {
     74    const { request, connector } = nextProps;
     75    fetchNetworkUpdatePacket(connector.requestData, request, [
     76      "requestHeaders",
     77      "responseHeaders",
     78      "requestPostData",
     79    ]);
     80  }
     81 
     82  /**
     83   * Parse a text representation of a name[divider]value list with
     84   * the given name regex and divider character.
     85   *
     86   * @param {string} text - Text of list
     87   * @return {Array} array of headers info {name, value}
     88   */
     89  parseRequestText(text, namereg, divider) {
     90    const regex = new RegExp(`(${namereg})\\${divider}\\s*(\\S.*)`);
     91    const pairs = [];
     92 
     93    for (const line of text.split("\n")) {
     94      const matches = regex.exec(line);
     95      if (matches) {
     96        const [, name, value] = matches;
     97        pairs.push({ name, value });
     98      }
     99    }
    100    return pairs;
    101  }
    102 
    103  /**
    104   * Update Custom Request Fields
    105   *
    106   * @param {object} evt click event
    107   * @param {object} request current request
    108   * @param {updateRequest} updateRequest action
    109   */
    110  updateCustomRequestFields(evt, request, updateRequest) {
    111    const val = evt.target.value;
    112    let data;
    113 
    114    switch (evt.target.id) {
    115      case "custom-headers-value":
    116        data = {
    117          requestHeaders: {
    118            customHeadersValue: val || "",
    119            // Parse text representation of multiple HTTP headers
    120            headers: this.parseRequestText(val, "\\S+?", ":"),
    121          },
    122        };
    123        break;
    124      case "custom-method-value":
    125        // If val is empty when leaving the "method" field, set the method to
    126        // its original value
    127        data =
    128          evt.type === "blur" && val === ""
    129            ? { method: this.initialRequestMethod }
    130            : { method: val.trim() };
    131        break;
    132      case "custom-postdata-value": {
    133        // Update "content-length" header value to reflect change
    134        // in post data field.
    135        const { requestHeaders } = request;
    136        const newHeaders = requestHeaders.headers.map(header => {
    137          if (header.name.toLowerCase() == "content-length") {
    138            return {
    139              name: header.name,
    140              value: val.length,
    141            };
    142          }
    143          return header;
    144        });
    145 
    146        data = {
    147          requestPostData: {
    148            postData: { text: val },
    149          },
    150          requestHeaders: {
    151            headers: newHeaders,
    152          },
    153        };
    154        break;
    155      }
    156      case "custom-query-value": {
    157        let customQueryValue = val || "";
    158        // Parse readable text list of a query string
    159        const queryArray = customQueryValue
    160          ? this.parseRequestText(customQueryValue, ".+?", "=")
    161          : [];
    162        // Write out a list of query params into a query string
    163        const queryString = queryArray
    164          .map(({ name, value }) => name + "=" + value)
    165          .join("&");
    166        const url = queryString
    167          ? [request.url.split("?")[0], queryString].join("?")
    168          : request.url.split("?")[0];
    169        // Remove temp customQueryValue while query string is parsable
    170        if (
    171          customQueryValue === "" ||
    172          queryArray.length === customQueryValue.split("\n").length
    173        ) {
    174          customQueryValue = null;
    175        }
    176        data = {
    177          customQueryValue,
    178          url,
    179        };
    180        break;
    181      }
    182      case "custom-url-value":
    183        data = {
    184          customQueryValue: null,
    185          url: val,
    186        };
    187        break;
    188      default:
    189        break;
    190    }
    191    if (data) {
    192      // All updateRequest batch mode should be disabled to make UI editing in sync
    193      updateRequest(request.id, data, false);
    194    }
    195  }
    196 
    197  render() {
    198    const {
    199      removeSelectedCustomRequest,
    200      request = {},
    201      sendCustomRequest,
    202      updateRequest,
    203    } = this.props;
    204    const { method, customQueryValue, requestHeaders, requestPostData, url } =
    205      request;
    206 
    207    let headers = "";
    208    if (requestHeaders) {
    209      headers = requestHeaders.customHeadersValue
    210        ? requestHeaders.customHeadersValue
    211        : writeHeaderText(requestHeaders.headers).trim();
    212    }
    213    const queryArray = url ? parseQueryString(getUrlQuery(url)) : [];
    214    let params = customQueryValue;
    215    if (!params) {
    216      params = queryArray
    217        ? queryArray.map(({ name, value }) => name + "=" + value).join("\n")
    218        : "";
    219    }
    220    const postData = requestPostData?.postData.text
    221      ? requestPostData.postData.text
    222      : "";
    223 
    224    return div(
    225      { className: "custom-request-panel" },
    226      div(
    227        { className: "custom-request-label custom-header" },
    228        CUSTOM_NEW_REQUEST
    229      ),
    230      div(
    231        { className: "custom-request-panel-content" },
    232        div(
    233          { className: "tabpanel-summary-container custom-request" },
    234          div(
    235            { className: "custom-request-button-container" },
    236            button(
    237              {
    238                className: "devtools-button",
    239                id: "custom-request-close-button",
    240                onClick: removeSelectedCustomRequest,
    241              },
    242              CUSTOM_CANCEL
    243            ),
    244            button(
    245              {
    246                className: "devtools-button",
    247                id: "custom-request-send-button",
    248                onClick: sendCustomRequest,
    249              },
    250              CUSTOM_SEND
    251            )
    252          )
    253        ),
    254        div(
    255          {
    256            className: "tabpanel-summary-container custom-method-and-url",
    257            id: "custom-method-and-url",
    258          },
    259          label(
    260            {
    261              className: "custom-method-value-label custom-request-label",
    262              htmlFor: "custom-method-value",
    263            },
    264            CUSTOM_NEW_REQUEST_METHOD_LABEL
    265          ),
    266          input({
    267            className: "custom-method-value",
    268            id: "custom-method-value",
    269            onChange: evt =>
    270              this.updateCustomRequestFields(evt, request, updateRequest),
    271            onBlur: evt =>
    272              this.updateCustomRequestFields(evt, request, updateRequest),
    273            value: method,
    274          }),
    275          label(
    276            {
    277              className: "custom-url-value-label custom-request-label",
    278              htmlFor: "custom-url-value",
    279            },
    280            CUSTOM_NEW_REQUEST_URL_LABEL
    281          ),
    282          input({
    283            className: "custom-url-value",
    284            id: "custom-url-value",
    285            onChange: evt =>
    286              this.updateCustomRequestFields(evt, request, updateRequest),
    287            value: url || "http://",
    288          })
    289        ),
    290        // Hide query field when there is no params
    291        params
    292          ? div(
    293              {
    294                className: "tabpanel-summary-container custom-section",
    295                id: "custom-query",
    296              },
    297              label(
    298                {
    299                  className: "custom-request-label",
    300                  htmlFor: "custom-query-value",
    301                },
    302                CUSTOM_QUERY
    303              ),
    304              textarea({
    305                className: "tabpanel-summary-input",
    306                id: "custom-query-value",
    307                onChange: evt =>
    308                  this.updateCustomRequestFields(evt, request, updateRequest),
    309                rows: 4,
    310                value: params,
    311                wrap: "off",
    312              })
    313            )
    314          : null,
    315        div(
    316          {
    317            id: "custom-headers",
    318            className: "tabpanel-summary-container custom-section",
    319          },
    320          label(
    321            {
    322              className: "custom-request-label",
    323              htmlFor: "custom-headers-value",
    324            },
    325            CUSTOM_HEADERS
    326          ),
    327          textarea({
    328            className: "tabpanel-summary-input",
    329            id: "custom-headers-value",
    330            onChange: evt =>
    331              this.updateCustomRequestFields(evt, request, updateRequest),
    332            rows: 8,
    333            value: headers,
    334            wrap: "off",
    335          })
    336        ),
    337        div(
    338          {
    339            id: "custom-postdata",
    340            className: "tabpanel-summary-container custom-section",
    341          },
    342          label(
    343            {
    344              className: "custom-request-label",
    345              htmlFor: "custom-postdata-value",
    346            },
    347            CUSTOM_POSTDATA
    348          ),
    349          textarea({
    350            className: "tabpanel-summary-input",
    351            id: "custom-postdata-value",
    352            onChange: evt =>
    353              this.updateCustomRequestFields(evt, request, updateRequest),
    354            rows: 6,
    355            value: postData,
    356            wrap: "off",
    357          })
    358        )
    359      )
    360    );
    361  }
    362 }
    363 
    364 module.exports = connect(
    365  state => ({ request: getSelectedRequest(state) }),
    366  dispatch => ({
    367    removeSelectedCustomRequest: () =>
    368      dispatch(Actions.removeSelectedCustomRequest()),
    369    sendCustomRequest: () => dispatch(Actions.sendCustomRequest()),
    370    updateRequest: (id, data, batch) =>
    371      dispatch(Actions.updateRequest(id, data, batch)),
    372  })
    373 )(CustomRequestPanel);