tor-browser

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

node-attribute-parser.js (12765B)


      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 /**
      8 * This module contains a small element attribute value parser. It's primary
      9 * goal is to extract link information from attribute values (like the href in
     10 * <a href="/some/link.html"> for example).
     11 *
     12 * There are several types of linkable attribute values:
     13 * - TYPE_URI: a URI (e.g. <a href="uri">).
     14 * - TYPE_URI_LIST: a space separated list of URIs (e.g. <a ping="uri1 uri2">).
     15 * - TYPE_IDREF: a reference to an other element in the same document via its id
     16 *   (e.g. <label for="input-id"> or <key command="command-id">).
     17 * - TYPE_IDREF_LIST: a space separated list of IDREFs (e.g.
     18 *   <output for="id1 id2">).
     19 * - TYPE_JS_RESOURCE_URI: a URI to a javascript resource that can be opened in
     20 *   the devtools (e.g. <script src="uri">).
     21 * - TYPE_CSS_RESOURCE_URI: a URI to a css resource that can be opened in the
     22 *   devtools (e.g. <link href="uri">).
     23 *
     24 * parseAttribute is the parser entry function, exported on this module.
     25 */
     26 
     27 const TYPE_STRING = "string";
     28 const TYPE_URI = "uri";
     29 const TYPE_URI_LIST = "uriList";
     30 const TYPE_IDREF = "idref";
     31 const TYPE_IDREF_LIST = "idrefList";
     32 const TYPE_JS_RESOURCE_URI = "jsresource";
     33 const TYPE_CSS_RESOURCE_URI = "cssresource";
     34 
     35 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     36 const HTML_NS = "http://www.w3.org/1999/xhtml";
     37 
     38 const WILDCARD = Symbol();
     39 
     40 const ATTRIBUTE_TYPES = new Map([
     41  ["action", { form: { namespaceURI: HTML_NS, type: TYPE_URI } }],
     42  [
     43    "aria-activedescendant",
     44    { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF } },
     45  ],
     46  [
     47    "aria-controls",
     48    { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF_LIST } },
     49  ],
     50  [
     51    "aria-describedby",
     52    { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF_LIST } },
     53  ],
     54  [
     55    "aria-details",
     56    { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF_LIST } },
     57  ],
     58  [
     59    "aria-errormessage",
     60    { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF } },
     61  ],
     62  [
     63    "aria-flowto",
     64    { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF_LIST } },
     65  ],
     66  [
     67    "aria-labelledby",
     68    { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF_LIST } },
     69  ],
     70  ["aria-owns", { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF_LIST } }],
     71  ["background", { body: { namespaceURI: HTML_NS, type: TYPE_URI } }],
     72  [
     73    "cite",
     74    {
     75      blockquote: { namespaceURI: HTML_NS, type: TYPE_URI },
     76      q: { namespaceURI: HTML_NS, type: TYPE_URI },
     77      del: { namespaceURI: HTML_NS, type: TYPE_URI },
     78      ins: { namespaceURI: HTML_NS, type: TYPE_URI },
     79    },
     80  ],
     81  ["classid", { object: { namespaceURI: HTML_NS, type: TYPE_URI } }],
     82  [
     83    "codebase",
     84    {
     85      object: { namespaceURI: HTML_NS, type: TYPE_URI },
     86      applet: { namespaceURI: HTML_NS, type: TYPE_URI },
     87    },
     88  ],
     89  [
     90    "command",
     91    {
     92      menuitem: { namespaceURI: HTML_NS, type: TYPE_IDREF },
     93      key: { namespaceURI: XUL_NS, type: TYPE_IDREF },
     94    },
     95  ],
     96  ["commandfor", { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF } }],
     97  [
     98    "contextmenu",
     99    {
    100      WILDCARD: { namespaceURI: WILDCARD, type: TYPE_IDREF },
    101    },
    102  ],
    103  ["data", { object: { namespaceURI: HTML_NS, type: TYPE_URI } }],
    104  [
    105    "for",
    106    {
    107      label: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    108      output: { namespaceURI: HTML_NS, type: TYPE_IDREF_LIST },
    109    },
    110  ],
    111  [
    112    "form",
    113    {
    114      button: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    115      fieldset: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    116      input: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    117      keygen: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    118      label: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    119      object: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    120      output: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    121      select: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    122      textarea: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    123    },
    124  ],
    125  [
    126    "formaction",
    127    {
    128      button: { namespaceURI: HTML_NS, type: TYPE_URI },
    129      input: { namespaceURI: HTML_NS, type: TYPE_URI },
    130    },
    131  ],
    132  [
    133    "headers",
    134    {
    135      td: { namespaceURI: HTML_NS, type: TYPE_IDREF_LIST },
    136      th: { namespaceURI: HTML_NS, type: TYPE_IDREF_LIST },
    137    },
    138  ],
    139  [
    140    "href",
    141    {
    142      a: { namespaceURI: HTML_NS, type: TYPE_URI },
    143      area: { namespaceURI: HTML_NS, type: TYPE_URI },
    144      link: [
    145        {
    146          namespaceURI: WILDCARD,
    147          type: TYPE_CSS_RESOURCE_URI,
    148          isValid: attributes => {
    149            return getAttribute(attributes, "rel") === "stylesheet";
    150          },
    151        },
    152        { namespaceURI: WILDCARD, type: TYPE_URI },
    153      ],
    154      base: { namespaceURI: HTML_NS, type: TYPE_URI },
    155    },
    156  ],
    157  [
    158    "icon",
    159    {
    160      menuitem: { namespaceURI: HTML_NS, type: TYPE_URI },
    161    },
    162  ],
    163  ["list", { input: { namespaceURI: HTML_NS, type: TYPE_IDREF } }],
    164  [
    165    "longdesc",
    166    {
    167      img: { namespaceURI: HTML_NS, type: TYPE_URI },
    168      frame: { namespaceURI: HTML_NS, type: TYPE_URI },
    169      iframe: { namespaceURI: HTML_NS, type: TYPE_URI },
    170    },
    171  ],
    172  ["manifest", { html: { namespaceURI: HTML_NS, type: TYPE_URI } }],
    173  [
    174    "menu",
    175    {
    176      button: { namespaceURI: HTML_NS, type: TYPE_IDREF },
    177      WILDCARD: { namespaceURI: XUL_NS, type: TYPE_IDREF },
    178    },
    179  ],
    180  [
    181    "ping",
    182    {
    183      a: { namespaceURI: HTML_NS, type: TYPE_URI_LIST },
    184      area: { namespaceURI: HTML_NS, type: TYPE_URI_LIST },
    185    },
    186  ],
    187  ["poster", { video: { namespaceURI: HTML_NS, type: TYPE_URI } }],
    188  ["profile", { head: { namespaceURI: HTML_NS, type: TYPE_URI } }],
    189  [
    190    "src",
    191    {
    192      script: { namespaceURI: WILDCARD, type: TYPE_JS_RESOURCE_URI },
    193      input: { namespaceURI: HTML_NS, type: TYPE_URI },
    194      frame: { namespaceURI: HTML_NS, type: TYPE_URI },
    195      iframe: { namespaceURI: HTML_NS, type: TYPE_URI },
    196      img: { namespaceURI: HTML_NS, type: TYPE_URI },
    197      audio: { namespaceURI: HTML_NS, type: TYPE_URI },
    198      embed: { namespaceURI: HTML_NS, type: TYPE_URI },
    199      source: { namespaceURI: HTML_NS, type: TYPE_URI },
    200      track: { namespaceURI: HTML_NS, type: TYPE_URI },
    201      video: { namespaceURI: HTML_NS, type: TYPE_URI },
    202      stringbundle: { namespaceURI: XUL_NS, type: TYPE_URI },
    203    },
    204  ],
    205  [
    206    "usemap",
    207    {
    208      img: { namespaceURI: HTML_NS, type: TYPE_URI },
    209      input: { namespaceURI: HTML_NS, type: TYPE_URI },
    210      object: { namespaceURI: HTML_NS, type: TYPE_URI },
    211    },
    212  ],
    213  ["xmlns", { WILDCARD: { namespaceURI: WILDCARD, type: TYPE_URI } }],
    214  ["containment", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_URI } }],
    215  ["context", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_IDREF } }],
    216  ["datasources", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_URI_LIST } }],
    217  ["insertafter", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_IDREF } }],
    218  ["insertbefore", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_IDREF } }],
    219  ["observes", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_IDREF } }],
    220  ["popovertarget", { WILDCARD: { namespaceURI: HTML_NS, type: TYPE_IDREF } }],
    221  ["popup", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_IDREF } }],
    222  ["ref", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_URI } }],
    223  ["removeelement", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_IDREF } }],
    224  ["template", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_IDREF } }],
    225  ["tooltip", { WILDCARD: { namespaceURI: XUL_NS, type: TYPE_IDREF } }],
    226  // SVG links aren't handled yet, see bug 1158831.
    227  // ["fill", {
    228  //   WILDCARD: {namespaceURI: SVG_NS, type: },
    229  // }],
    230  // ["stroke", {
    231  //   WILDCARD: {namespaceURI: SVG_NS, type: },
    232  // }],
    233  // ["markerstart", {
    234  //   WILDCARD: {namespaceURI: SVG_NS, type: },
    235  // }],
    236  // ["markermid", {
    237  //   WILDCARD: {namespaceURI: SVG_NS, type: },
    238  // }],
    239  // ["markerend", {
    240  //   WILDCARD: {namespaceURI: SVG_NS, type: },
    241  // }],
    242  // ["xlink:href", {
    243  //   WILDCARD: {namespaceURI: SVG_NS, type: },
    244  // }],
    245 ]);
    246 
    247 var parsers = {
    248  [TYPE_URI](attributeValue) {
    249    return [
    250      {
    251        type: TYPE_URI,
    252        value: attributeValue,
    253      },
    254    ];
    255  },
    256  [TYPE_URI_LIST](attributeValue) {
    257    const data = splitBy(attributeValue, " ");
    258    for (const token of data) {
    259      if (!token.type) {
    260        token.type = TYPE_URI;
    261      }
    262    }
    263    return data;
    264  },
    265  [TYPE_JS_RESOURCE_URI](attributeValue) {
    266    return [
    267      {
    268        type: TYPE_JS_RESOURCE_URI,
    269        value: attributeValue,
    270      },
    271    ];
    272  },
    273  [TYPE_CSS_RESOURCE_URI](attributeValue) {
    274    return [
    275      {
    276        type: TYPE_CSS_RESOURCE_URI,
    277        value: attributeValue,
    278      },
    279    ];
    280  },
    281  [TYPE_IDREF](attributeValue) {
    282    return [
    283      {
    284        type: TYPE_IDREF,
    285        value: attributeValue,
    286      },
    287    ];
    288  },
    289  [TYPE_IDREF_LIST](attributeValue) {
    290    const data = splitBy(attributeValue, " ");
    291    for (const token of data) {
    292      if (!token.type) {
    293        token.type = TYPE_IDREF;
    294      }
    295    }
    296    return data;
    297  },
    298 };
    299 
    300 /**
    301 * Parse an attribute value.
    302 *
    303 * @param {string} namespaceURI The namespaceURI of the node that has the
    304 * attribute.
    305 * @param {string} tagName The tagName of the node that has the attribute.
    306 * @param {Array} attributes The list of all attributes of the node. This should
    307 * be an array of {name, value} objects.
    308 * @param {string} attributeName The name of the attribute to parse.
    309 * @param {string} attributeValue The value of the attribute to parse.
    310 * @return {Array} An array of tokens that represents the value. Each token is
    311 * an object {type: [string|uri|jsresource|cssresource|idref], value}.
    312 * For instance parsing the ping attribute in <a ping="uri1 uri2"> returns:
    313 * [
    314 *   {type: "uri", value: "uri2"},
    315 *   {type: "string", value: " "},
    316 *   {type: "uri", value: "uri1"}
    317 * ]
    318 */
    319 function parseAttribute(
    320  namespaceURI,
    321  tagName,
    322  attributes,
    323  attributeName,
    324  attributeValue
    325 ) {
    326  const type = getType(namespaceURI, tagName, attributes, attributeName);
    327  if (!type) {
    328    return [
    329      {
    330        type: TYPE_STRING,
    331        value: attributeValue,
    332      },
    333    ];
    334  }
    335 
    336  return parsers[type](attributeValue);
    337 }
    338 
    339 /**
    340 * Get the type for links in this attribute if any.
    341 *
    342 * @param {string} namespaceURI The node's namespaceURI.
    343 * @param {string} tagName The node's tagName.
    344 * @param {Array} attributes The node's attributes, as a list of {name, value}
    345 * objects.
    346 * @param {string} attributeName The name of the attribute to get the type for.
    347 * @return {object} null if no type exist for this attribute on this node, the
    348 * type object otherwise.
    349 */
    350 function getType(namespaceURI, tagName, attributes, attributeName) {
    351  const attributeType = ATTRIBUTE_TYPES.get(attributeName);
    352  if (!attributeType) {
    353    return null;
    354  }
    355 
    356  const lcTagName = tagName.toLowerCase();
    357  const typeData = attributeType[lcTagName] || attributeType.WILDCARD;
    358 
    359  if (!typeData) {
    360    return null;
    361  }
    362 
    363  if (Array.isArray(typeData)) {
    364    for (const data of typeData) {
    365      const hasNamespace =
    366        data.namespaceURI === WILDCARD || data.namespaceURI === namespaceURI;
    367      const isValid = data.isValid ? data.isValid(attributes) : true;
    368 
    369      if (hasNamespace && isValid) {
    370        return data.type;
    371      }
    372    }
    373 
    374    return null;
    375  } else if (
    376    typeData.namespaceURI === WILDCARD ||
    377    typeData.namespaceURI === namespaceURI
    378  ) {
    379    return typeData.type;
    380  }
    381 
    382  return null;
    383 }
    384 
    385 function getAttribute(attributes, attributeName) {
    386  const attribute = attributes.find(x => x.name === attributeName);
    387  return attribute ? attribute.value : null;
    388 }
    389 
    390 /**
    391 * Split a string by a given character and return an array of objects parts.
    392 * The array will contain objects for the split character too, marked with
    393 * TYPE_STRING type.
    394 *
    395 * @param {string} value The string to parse.
    396 * @param {string} splitChar A 1 length split character.
    397 * @return {Array}
    398 */
    399 function splitBy(value, splitChar) {
    400  const data = [];
    401 
    402  let i = 0,
    403    buffer = "";
    404  while (i <= value.length) {
    405    if (i === value.length && buffer) {
    406      data.push({ value: buffer });
    407    }
    408    if (value[i] === splitChar) {
    409      if (buffer) {
    410        data.push({ value: buffer });
    411      }
    412      data.push({
    413        type: TYPE_STRING,
    414        value: splitChar,
    415      });
    416      buffer = "";
    417    } else {
    418      buffer += value[i];
    419    }
    420 
    421    i++;
    422  }
    423  return data;
    424 }
    425 
    426 exports.parseAttribute = parseAttribute;
    427 exports.ATTRIBUTE_TYPES = {
    428  TYPE_STRING,
    429  TYPE_URI,
    430  TYPE_URI_LIST,
    431  TYPE_IDREF,
    432  TYPE_IDREF_LIST,
    433  TYPE_JS_RESOURCE_URI,
    434  TYPE_CSS_RESOURCE_URI,
    435 };
    436 
    437 // Exported for testing only.
    438 exports.splitBy = splitBy;