summaryrefslogtreecommitdiffstats
path: root/devtools/shared/storage/utils.js
blob: 8a455d2c6c883aceab86a8b6e9b8350a65aa1eb1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

loader.lazyRequireGetter(
  this,
  "validator",
  "resource://devtools/shared/storage/vendor/stringvalidator/validator.js"
);
loader.lazyRequireGetter(
  this,
  "JSON5",
  "resource://devtools/shared/storage/vendor/json5.js"
);

const MATH_REGEX = /(?:(?:^|[-+_*/])(?:\s*-?\d+(\.\d+)?(?:[eE][+-]?\d+)?\s*))+$/;

/**
 * Tries to parse a string into an object on the basis of key-value pairs,
 * separated by various separators. If failed, tries to parse for single
 * separator separated values to form an array.
 *
 * @param {string} value
 *        The string to be parsed into an object or array
 */
function _extractKeyValPairs(value) {
  const makeObject = (keySep, pairSep) => {
    const object = {};
    for (const pair of value.split(pairSep)) {
      const [key, val] = pair.split(keySep);
      object[key] = val;
    }
    return object;
  };

  // Possible separators.
  const separators = ["=", ":", "~", "#", "&", "\\*", ",", "\\."];
  // Testing for object
  for (let i = 0; i < separators.length; i++) {
    const kv = separators[i];
    for (let j = 0; j < separators.length; j++) {
      if (i == j) {
        continue;
      }
      const p = separators[j];
      const word = `[^${kv}${p}]*`;
      const keyValue = `${word}${kv}${word}`;
      const keyValueList = `${keyValue}(${p}${keyValue})*`;
      const regex = new RegExp(`^${keyValueList}$`);
      if (
        value.match &&
        value.match(regex) &&
        value.includes(kv) &&
        (value.includes(p) || value.split(kv).length == 2)
      ) {
        return makeObject(kv, p);
      }
    }
  }
  // Testing for array
  for (const p of separators) {
    const word = `[^${p}]*`;
    const wordList = `(${word}${p})+${word}`;
    const regex = new RegExp(`^${wordList}$`);

    if (regex.test(value)) {
      const pNoBackslash = p.replace(/\\*/g, "");
      return value.split(pNoBackslash);
    }
  }
  return null;
}

/**
 * Check whether the value string represents something that should be
 * displayed as text. If so then it shouldn't be parsed into a tree.
 *
 * @param  {String} value
 *         The value to be parsed.
 */
function _shouldParse(value) {
  const validators = [
    "isBase64",
    "isBoolean",
    "isCurrency",
    "isDataURI",
    "isEmail",
    "isFQDN",
    "isHexColor",
    "isIP",
    "isISO8601",
    "isMACAddress",
    "isSemVer",
    "isURL",
  ];

  // Check for minus calculations e.g. 8-3 because otherwise 5 will be displayed.
  if (MATH_REGEX.test(value)) {
    return false;
  }

  // Check for any other types that shouldn't be parsed.
  for (const test of validators) {
    if (validator[test](value)) {
      return false;
    }
  }

  // Seems like this is data that should be parsed.
  return true;
}

/**
 * Tries to parse a string value into either a json or a key-value separated
 * object. The value can also be a key separated array.
 *
 * @param {string} originalValue
 *        The string to be parsed into an object
 */
function parseItemValue(originalValue) {
  // Find if value is URLEncoded ie
  let decodedValue = "";
  try {
    decodedValue = decodeURIComponent(originalValue);
  } catch (e) {
    // Unable to decode, nothing to do
  }
  const value =
    decodedValue && decodedValue !== originalValue
      ? decodedValue
      : originalValue;

  if (!_shouldParse(value)) {
    return value;
  }

  let obj = null;
  try {
    obj = JSON5.parse(value);
  } catch (ex) {
    obj = null;
  }

  if (!obj && value) {
    obj = _extractKeyValPairs(value);
  }

  // return if obj is null, or same as value, or just a string.
  if (!obj || obj === value || typeof obj === "string") {
    return value;
  }

  // If we got this far, originalValue is an object literal or array,
  // and we have successfully parsed it
  return obj;
}

exports.parseItemValue = parseItemValue;