tor-browser

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

UrlPreview.js (8435B)


      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 const {
      7  Component,
      8  createFactory,
      9 } = require("resource://devtools/client/shared/vendor/react.mjs");
     10 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     11 
     12 const PropertiesView = createFactory(
     13  require("resource://devtools/client/netmonitor/src/components/request-details/PropertiesView.js")
     14 );
     15 const {
     16  L10N,
     17 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js");
     18 const {
     19  parseQueryString,
     20 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
     21 
     22 const TreeRow = createFactory(
     23  ChromeUtils.importESModule(
     24    "resource://devtools/client/shared/components/tree/TreeRow.mjs",
     25    { global: "current" }
     26  ).default
     27 );
     28 
     29 loader.lazyGetter(this, "MODE", function () {
     30  return ChromeUtils.importESModule(
     31    "resource://devtools/client/shared/components/reps/index.mjs"
     32  ).MODE;
     33 });
     34 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     35 
     36 const { div, span, tr, td } = dom;
     37 
     38 /**
     39 * Url Preview Component
     40 * This component is used to render urls. Its show both compact and destructured views
     41 * of the url. Its takes a url and the http method as properties.
     42 *
     43 * Example Url:
     44 * https://foo.com/bla?x=123&y=456&z=789&a=foo&a=bar
     45 *
     46 * Structure:
     47 * {
     48 *   GET : {
     49 *    "scheme" : "https",
     50 *    "host" : "foo.com",
     51 *    "filename" : "bla",
     52 *    "query" : {
     53 *      "x": "123",
     54 *      "y": "456",
     55 *      "z": "789",
     56 *      "a": {
     57 *         "0": foo,
     58 *         "1": bar
     59 *       }
     60 *     },
     61 *     "remote" :  {
     62 *        "address" : "127.0.0.1:8080"
     63 *     }
     64 *   }
     65 * }
     66 */
     67 class UrlPreview extends Component {
     68  static get propTypes() {
     69    return {
     70      url: PropTypes.string,
     71      method: PropTypes.string,
     72      address: PropTypes.string,
     73      proxyStatus: PropTypes.string,
     74      shouldExpandPreview: PropTypes.bool,
     75      onTogglePreview: PropTypes.func,
     76    };
     77  }
     78 
     79  constructor(props) {
     80    super(props);
     81    this.parseUrl = this.parseUrl.bind(this);
     82    this.renderValue = this.renderValue.bind(this);
     83  }
     84 
     85  shouldComponentUpdate(nextProps) {
     86    return (
     87      nextProps.url !== this.props.url ||
     88      nextProps.method !== this.props.method ||
     89      nextProps.address !== this.props.address
     90    );
     91  }
     92 
     93  renderRow(props) {
     94    const {
     95      member: { name, level },
     96    } = props;
     97    if ((name == "query" || name == "remote") && level == 1) {
     98      return tr(
     99        { key: name, className: "treeRow stringRow" },
    100        td(
    101          { colSpan: 2, className: "splitter" },
    102          div({ className: "horizontal-splitter" })
    103        )
    104      );
    105    }
    106 
    107    const customProps = { ...props };
    108    customProps.member.selected = false;
    109    return TreeRow(customProps);
    110  }
    111 
    112  renderValue(props) {
    113    const {
    114      member: { level, open },
    115      value,
    116    } = props;
    117    if (level == 0) {
    118      if (open) {
    119        return "";
    120      }
    121      const { scheme, host, filename, query } = value;
    122      const queryParamNames = query ? Object.keys(query) : [];
    123      // render collapsed url
    124      return div(
    125        { key: "url", className: "url" },
    126        span({ key: "url-scheme", className: "url-scheme" }, `${scheme}://`),
    127        span({ key: "url-host", className: "url-host" }, `${host}`),
    128        span({ key: "url-filename", className: "url-filename" }, `${filename}`),
    129        !!queryParamNames.length &&
    130          span({ key: "url-ques", className: "url-chars" }, "?"),
    131 
    132        queryParamNames.map((name, index) => {
    133          if (Array.isArray(query[name])) {
    134            return query[name].map((item, queryIndex) => {
    135              return span(
    136                {
    137                  key: `url-params-${name}${queryIndex}`,
    138                  className: "url-params",
    139                },
    140                span(
    141                  {
    142                    key: `url-params${name}${queryIndex}-name`,
    143                    className: "url-params-name",
    144                  },
    145                  `${name}`
    146                ),
    147                span(
    148                  {
    149                    key: `url-chars-${name}${queryIndex}-equals`,
    150                    className: "url-chars",
    151                  },
    152                  "="
    153                ),
    154                span(
    155                  {
    156                    key: `url-params-${name}${queryIndex}-value`,
    157                    className: "url-params-value",
    158                  },
    159                  `${item}`
    160                ),
    161                (query[name].length - 1 !== queryIndex ||
    162                  queryParamNames.length - 1 !== index) &&
    163                  span({ key: "url-amp", className: "url-chars" }, "&")
    164              );
    165            });
    166          }
    167 
    168          return span(
    169            { key: `url-params-${name}`, className: "url-params" },
    170            span(
    171              { key: "url-params-name", className: "url-params-name" },
    172              `${name}`
    173            ),
    174            span({ key: "url-chars-equals", className: "url-chars" }, "="),
    175            span(
    176              { key: "url-params-value", className: "url-params-value" },
    177              `${query[name]}`
    178            ),
    179            queryParamNames.length - 1 !== index &&
    180              span({ key: "url-amp", className: "url-chars" }, "&")
    181          );
    182        })
    183      );
    184    }
    185    if (typeof value !== "string") {
    186      // the query node would be an object
    187      if (level == 0) {
    188        return "";
    189      }
    190      // for arrays (multival)
    191      return "[...]";
    192    }
    193 
    194    return value;
    195  }
    196 
    197  parseUrl(url) {
    198    const { method, address, proxyStatus } = this.props;
    199    const { host, protocol, pathname, search } = new URL(url);
    200 
    201    const urlObject = {
    202      [method]: {
    203        scheme: protocol.replace(":", ""),
    204        host,
    205        filename: pathname,
    206      },
    207    };
    208 
    209    const expandedNodes = new Set();
    210 
    211    // check and add query parameters
    212    if (search.length) {
    213      const params = parseQueryString(search);
    214      // make sure the query node is always expanded
    215      expandedNodes.add(`/${method}/query`);
    216      urlObject[method].query = params.reduce((map, obj) => {
    217        const value = map[obj.name];
    218        if (value || value === "") {
    219          if (typeof value !== "object") {
    220            expandedNodes.add(`/${method}/query/${obj.name}`);
    221            map[obj.name] = [value];
    222          }
    223          map[obj.name].push(obj.value);
    224        } else {
    225          map[obj.name] = obj.value;
    226        }
    227        return map;
    228      }, Object.create(null));
    229    }
    230 
    231    if (address) {
    232      // makes sure the remote address section is expanded
    233      expandedNodes.add(`/${method}/remote`);
    234      urlObject[method].remote = {
    235        [L10N.getStr(
    236          proxyStatus
    237            ? "netmonitor.headers.proxyAddress"
    238            : "netmonitor.headers.address"
    239        )]: address,
    240      };
    241    }
    242 
    243    return {
    244      urlObject,
    245      expandedNodes,
    246    };
    247  }
    248 
    249  render() {
    250    const {
    251      url,
    252      method,
    253      shouldExpandPreview = false,
    254      onTogglePreview,
    255    } = this.props;
    256 
    257    const { urlObject, expandedNodes } = this.parseUrl(url);
    258 
    259    if (shouldExpandPreview) {
    260      expandedNodes.add(`/${method}`);
    261    }
    262 
    263    return div(
    264      { className: "url-preview" },
    265      PropertiesView({
    266        object: urlObject,
    267        useQuotes: true,
    268        defaultSelectFirstNode: false,
    269        mode: MODE.TINY,
    270        expandedNodes,
    271        renderRow: this.renderRow,
    272        renderValue: this.renderValue,
    273        enableInput: false,
    274        onClickRow: (path, evt, member) => {
    275          // Only track when the root is toggled
    276          // as all the others are always expanded by
    277          // default.
    278          if (path == `/${method}`) {
    279            onTogglePreview(!member.open);
    280          }
    281        },
    282        contextMenuFormatters: {
    283          copyFormatter: (member, baseCopyFormatter) => {
    284            const { value, level, hasChildren } = member;
    285            if (hasChildren && level == 0) {
    286              const { scheme, filename, host, query } = value;
    287              return `${scheme}://${host}${filename}${
    288                query ? "?" + new URLSearchParams(query).toString() : ""
    289              }`;
    290            }
    291            return baseCopyFormatter(member);
    292          },
    293        },
    294      })
    295    );
    296  }
    297 }
    298 
    299 module.exports = UrlPreview;