diff options
Diffstat (limited to 'devtools/client/webconsole/net/utils')
-rw-r--r-- | devtools/client/webconsole/net/utils/events.js | 21 | ||||
-rw-r--r-- | devtools/client/webconsole/net/utils/json.js | 234 | ||||
-rw-r--r-- | devtools/client/webconsole/net/utils/moz.build | 11 | ||||
-rw-r--r-- | devtools/client/webconsole/net/utils/net.js | 134 |
4 files changed, 400 insertions, 0 deletions
diff --git a/devtools/client/webconsole/net/utils/events.js b/devtools/client/webconsole/net/utils/events.js new file mode 100644 index 000000000..9f8705593 --- /dev/null +++ b/devtools/client/webconsole/net/utils/events.js @@ -0,0 +1,21 @@ +/* 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"; + +function isLeftClick(event, allowKeyModifiers) { + return event.button === 0 && (allowKeyModifiers || noKeyModifiers(event)); +} + +function noKeyModifiers(event) { + return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey; +} + +function cancelEvent(event) { + event.stopPropagation(); + event.preventDefault(); +} + +// Exports from this module +exports.isLeftClick = isLeftClick; +exports.cancelEvent = cancelEvent; 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; + diff --git a/devtools/client/webconsole/net/utils/moz.build b/devtools/client/webconsole/net/utils/moz.build new file mode 100644 index 000000000..3fdc458e3 --- /dev/null +++ b/devtools/client/webconsole/net/utils/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DevToolsModules( + 'events.js', + 'json.js', + 'net.js', +) diff --git a/devtools/client/webconsole/net/utils/net.js b/devtools/client/webconsole/net/utils/net.js new file mode 100644 index 000000000..782ec032a --- /dev/null +++ b/devtools/client/webconsole/net/utils/net.js @@ -0,0 +1,134 @@ +/* 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"; + +const mimeCategoryMap = { + "text/plain": "txt", + "application/octet-stream": "bin", + "text/html": "html", + "text/xml": "html", + "application/xml": "html", + "application/rss+xml": "html", + "application/atom+xml": "html", + "application/xhtml+xml": "html", + "application/mathml+xml": "html", + "application/rdf+xml": "html", + "text/css": "css", + "application/x-javascript": "js", + "text/javascript": "js", + "application/javascript": "js", + "text/ecmascript": "js", + "application/ecmascript": "js", + "image/jpeg": "image", + "image/jpg": "image", + "image/gif": "image", + "image/png": "image", + "image/bmp": "image", + "application/x-shockwave-flash": "plugin", + "application/x-silverlight-app": "plugin", + "video/x-flv": "media", + "audio/mpeg3": "media", + "audio/x-mpeg-3": "media", + "video/mpeg": "media", + "video/x-mpeg": "media", + "video/webm": "media", + "video/mp4": "media", + "video/ogg": "media", + "audio/ogg": "media", + "application/ogg": "media", + "application/x-ogg": "media", + "application/x-midi": "media", + "audio/midi": "media", + "audio/x-mid": "media", + "audio/x-midi": "media", + "music/crescendo": "media", + "audio/wav": "media", + "audio/x-wav": "media", + "application/x-woff": "font", + "application/font-woff": "font", + "application/x-font-woff": "font", + "application/x-ttf": "font", + "application/x-font-ttf": "font", + "font/ttf": "font", + "font/woff": "font", + "application/x-otf": "font", + "application/x-font-otf": "font" +}; + +var NetUtils = {}; + +NetUtils.isImage = function (contentType) { + if (!contentType) { + return false; + } + + contentType = contentType.split(";")[0]; + contentType = contentType.trim(); + return mimeCategoryMap[contentType] == "image"; +}; + +NetUtils.isHTML = function (contentType) { + if (!contentType) { + return false; + } + + contentType = contentType.split(";")[0]; + contentType = contentType.trim(); + return mimeCategoryMap[contentType] == "html"; +}; + +NetUtils.getHeaderValue = function (headers, name) { + if (!headers) { + return null; + } + + name = name.toLowerCase(); + for (let i = 0; i < headers.length; ++i) { + let headerName = headers[i].name.toLowerCase(); + if (headerName == name) { + return headers[i].value; + } + } +}; + +NetUtils.parseXml = function (content) { + let contentType = content.mimeType.split(";")[0]; + contentType = contentType.trim(); + + let parser = new DOMParser(); + let doc = parser.parseFromString(content.text, contentType); + let root = doc.documentElement; + + // Error handling + let nsURI = "http://www.mozilla.org/newlayout/xml/parsererror.xml"; + if (root.namespaceURI == nsURI && root.nodeName == "parsererror") { + return null; + } + + return doc; +}; + +NetUtils.isURLEncodedRequest = function (file) { + let mimeType = "application/x-www-form-urlencoded"; + + let postData = file.request.postData; + if (postData && postData.text) { + let text = postData.text.toLowerCase(); + if (text.startsWith("content-type: " + mimeType)) { + return true; + } + } + + let value = NetUtils.getHeaderValue(file.request.headers, "content-type"); + return value && value.startsWith(mimeType); +}; + +NetUtils.isMultiPartRequest = function (file) { + let mimeType = "multipart/form-data"; + let value = NetUtils.getHeaderValue(file.request.headers, "content-type"); + return value && value.startsWith(mimeType); +}; + +// Exports from this module +module.exports = NetUtils; |