diff options
Diffstat (limited to 'devtools/client/webconsole/net/utils/json.js')
-rw-r--r-- | devtools/client/webconsole/net/utils/json.js | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/devtools/client/webconsole/net/utils/json.js b/devtools/client/webconsole/net/utils/json.js new file mode 100644 index 000000000..70d733f28 --- /dev/null +++ b/devtools/client/webconsole/net/utils/json.js @@ -0,0 +1,234 @@ +/* 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"; + +// List of JSON content types. +const contentTypes = { + "text/plain": 1, + "text/javascript": 1, + "text/x-javascript": 1, + "text/json": 1, + "text/x-json": 1, + "application/json": 1, + "application/x-json": 1, + "application/javascript": 1, + "application/x-javascript": 1, + "application/json-rpc": 1 +}; + +// Implementation +var Json = {}; + +/** + * Parsing JSON + */ +Json.parseJSONString = function (jsonString) { + if (!jsonString.length) { + return null; + } + + let regex, matches; + + let first = firstNonWs(jsonString); + if (first !== "[" && first !== "{") { + // This (probably) isn't pure JSON. Let's try to strip various sorts + // of XSSI protection/wrapping and see if that works better. + + // Prototype-style secure requests + regex = /^\s*\/\*-secure-([\s\S]*)\*\/\s*$/; + matches = regex.exec(jsonString); + if (matches) { + jsonString = matches[1]; + + if (jsonString[0] === "\\" && jsonString[1] === "n") { + jsonString = jsonString.substr(2); + } + + if (jsonString[jsonString.length - 2] === "\\" && + jsonString[jsonString.length - 1] === "n") { + jsonString = jsonString.substr(0, jsonString.length - 2); + } + } + + // Google-style (?) delimiters + if (jsonString.indexOf("&&&START&&&") !== -1) { + regex = /&&&START&&&([\s\S]*)&&&END&&&/; + matches = regex.exec(jsonString); + if (matches) { + jsonString = matches[1]; + } + } + + // while(1);, for(;;);, and )]}' + regex = /^\s*(\)\]\}[^\n]*\n|while\s*\(1\);|for\s*\(;;\);)([\s\S]*)/; + matches = regex.exec(jsonString); + if (matches) { + jsonString = matches[2]; + } + + // JSONP + regex = /^\s*([A-Za-z0-9_$.]+\s*(?:\[.*\]|))\s*\(([\s\S]*)\)/; + matches = regex.exec(jsonString); + if (matches) { + jsonString = matches[2]; + } + } + + try { + return JSON.parse(jsonString); + } catch (err) { + // eslint-disable-line no-empty + } + + // Give up if we don't have valid start, to avoid some unnecessary overhead. + first = firstNonWs(jsonString); + if (first !== "[" && first !== "{" && isNaN(first) && first !== '"') { + return null; + } + + // Remove JavaScript comments, quote non-quoted identifiers, and merge + // multi-line structures like |{"a": 1} \n {"b": 2}| into a single JSON + // object [{"a": 1}, {"b": 2}]. + jsonString = pseudoJsonToJson(jsonString); + + try { + return JSON.parse(jsonString); + } catch (err) { + // eslint-disable-line no-empty + } + + return null; +}; + +function firstNonWs(str) { + for (let i = 0, len = str.length; i < len; i++) { + let ch = str[i]; + if (ch !== " " && ch !== "\n" && ch !== "\t" && ch !== "\r") { + return ch; + } + } + return ""; +} + +function pseudoJsonToJson(json) { + let ret = ""; + let at = 0, lasti = 0, lastch = "", hasMultipleParts = false; + for (let i = 0, len = json.length; i < len; ++i) { + let ch = json[i]; + if (/\s/.test(ch)) { + continue; + } + + if (ch === '"') { + // Consume a string. + ++i; + while (i < len) { + if (json[i] === "\\") { + ++i; + } else if (json[i] === '"') { + break; + } + ++i; + } + } else if (ch === "'") { + // Convert an invalid string into a valid one. + ret += json.slice(at, i) + "\""; + at = i + 1; + ++i; + + while (i < len) { + if (json[i] === "\\") { + ++i; + } else if (json[i] === "'") { + break; + } + ++i; + } + + if (i < len) { + ret += json.slice(at, i) + "\""; + at = i + 1; + } + } else if ((ch === "[" || ch === "{") && + (lastch === "]" || lastch === "}")) { + // Multiple JSON messages in one... Make it into a single array by + // inserting a comma and setting the "multiple parts" flag. + ret += json.slice(at, i) + ","; + hasMultipleParts = true; + at = i; + } else if (lastch === "," && (ch === "]" || ch === "}")) { + // Trailing commas in arrays/objects. + ret += json.slice(at, lasti); + at = i; + } else if (lastch === "/" && lasti === i - 1) { + // Some kind of comment; remove it. + if (ch === "/") { + ret += json.slice(at, i - 1); + at = i + json.slice(i).search(/\n|\r|$/); + i = at - 1; + } else if (ch === "*") { + ret += json.slice(at, i - 1); + at = json.indexOf("*/", i + 1) + 2; + if (at === 1) { + at = len; + } + i = at - 1; + } + ch = "\0"; + } else if (/[a-zA-Z$_]/.test(ch) && lastch !== ":") { + // Non-quoted identifier. Quote it. + ret += json.slice(at, i) + "\""; + at = i; + i = i + json.slice(i).search(/[^a-zA-Z0-9$_]|$/); + ret += json.slice(at, i) + "\""; + at = i; + } + + lastch = ch; + lasti = i; + } + + ret += json.slice(at); + if (hasMultipleParts) { + ret = "[" + ret + "]"; + } + + return ret; +} + +Json.isJSON = function (contentType, data) { + // Workaround for JSON responses without proper content type + // Let's consider all responses starting with "{" as JSON. In the worst + // case there will be an exception when parsing. This means that no-JSON + // responses (and post data) (with "{") can be parsed unnecessarily, + // which represents a little overhead, but this happens only if the request + // is actually expanded by the user in the UI (Net & Console panels). + // Do a manual string search instead of checking (data.strip()[0] === "{") + // to improve performance/memory usage. + let len = data ? data.length : 0; + for (let i = 0; i < len; i++) { + let ch = data.charAt(i); + if (ch === "{") { + return true; + } + + if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") { + continue; + } + + break; + } + + if (!contentType) { + return false; + } + + contentType = contentType.split(";")[0]; + contentType = contentType.trim(); + return !!contentTypes[contentType]; +}; + +// Exports from this module +module.exports = Json; + |