diff options
Diffstat (limited to 'devtools/client/webconsole/net/components/post-tab.js')
-rw-r--r-- | devtools/client/webconsole/net/components/post-tab.js | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/devtools/client/webconsole/net/components/post-tab.js b/devtools/client/webconsole/net/components/post-tab.js new file mode 100644 index 000000000..6d06eb40b --- /dev/null +++ b/devtools/client/webconsole/net/components/post-tab.js @@ -0,0 +1,279 @@ +/* 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 React = require("devtools/client/shared/vendor/react"); + +// Reps +const { createFactories, parseURLEncodedText } = require("devtools/client/shared/components/reps/rep-utils"); +const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view")); +const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep")); + +// Network +const NetInfoParams = React.createFactory(require("./net-info-params")); +const NetInfoGroupList = React.createFactory(require("./net-info-group-list")); +const Spinner = React.createFactory(require("./spinner")); +const SizeLimit = React.createFactory(require("./size-limit")); +const NetUtils = require("../utils/net"); +const Json = require("../utils/json"); + +// Shortcuts +const DOM = React.DOM; +const PropTypes = React.PropTypes; + +/** + * This template represents 'Post' tab displayed when the user + * expands network log in the Console panel. It's responsible for + * displaying posted data (HTTP post body). + */ +var PostTab = React.createClass({ + propTypes: { + data: PropTypes.shape({ + request: PropTypes.object.isRequired + }), + actions: PropTypes.object.isRequired + }, + + displayName: "PostTab", + + isJson(file) { + let text = file.request.postData.text; + let value = NetUtils.getHeaderValue(file.request.headers, "content-type"); + return Json.isJSON(value, text); + }, + + parseJson(file) { + let postData = file.request.postData; + if (!postData) { + return null; + } + + let jsonString = new String(postData.text); + return Json.parseJSONString(jsonString); + }, + + /** + * Render JSON post data as an expandable tree. + */ + renderJson(file) { + let text = file.request.postData.text; + if (!text || isLongString(text)) { + return null; + } + + if (!this.isJson(file)) { + return null; + } + + let json = this.parseJson(file); + if (!json) { + return null; + } + + return { + key: "json", + content: TreeView({ + columns: [{id: "value"}], + object: json, + mode: "tiny", + renderValue: props => Rep(Object.assign({}, props, { + cropLimit: 50, + })), + }), + name: Locale.$STR("jsonScopeName") + }; + }, + + parseXml(file) { + let text = file.request.postData.text; + if (isLongString(text)) { + return null; + } + + return NetUtils.parseXml({ + mimeType: NetUtils.getHeaderValue(file.request.headers, "content-type"), + text: text, + }); + }, + + isXml(file) { + if (isLongString(file.request.postData.text)) { + return false; + } + + let value = NetUtils.getHeaderValue(file.request.headers, "content-type"); + if (!value) { + return false; + } + + return NetUtils.isHTML(value); + }, + + renderXml(file) { + let text = file.request.postData.text; + if (!text || isLongString(text)) { + return null; + } + + if (!this.isXml(file)) { + return null; + } + + let doc = this.parseXml(file); + if (!doc) { + return null; + } + + // Proper component for rendering XML should be used (see bug 1247392) + return null; + }, + + /** + * Multipart post data are parsed and nicely rendered + * as an expandable tree of individual parts. + */ + renderMultiPart(file) { + let text = file.request.postData.text; + if (!text || isLongString(text)) { + return; + } + + if (NetUtils.isMultiPartRequest(file)) { + // TODO: render multi part request (bug: 1247423) + } + + return; + }, + + /** + * URL encoded post data are nicely rendered as a list + * of parameters. + */ + renderUrlEncoded(file) { + let text = file.request.postData.text; + if (!text || isLongString(text)) { + return null; + } + + if (!NetUtils.isURLEncodedRequest(file)) { + return null; + } + + let lines = text.split("\n"); + let params = parseURLEncodedText(lines[lines.length - 1]); + + return { + key: "url-encoded", + content: NetInfoParams({params: params}), + name: Locale.$STR("netRequest.params") + }; + }, + + renderRawData(file) { + let text = file.request.postData.text; + + let group; + + // The post body might reached the limit, so check if we are + // dealing with a long string. + if (typeof text == "object") { + group = { + key: "raw-longstring", + name: Locale.$STR("netRequest.rawData"), + content: DOM.div({className: "netInfoResponseContent"}, + sanitize(text.initial), + SizeLimit({ + actions: this.props.actions, + data: file.request.postData, + message: Locale.$STR("netRequest.sizeLimitMessage"), + link: Locale.$STR("netRequest.sizeLimitMessageLink") + }) + ) + }; + } else { + group = { + key: "raw", + name: Locale.$STR("netRequest.rawData"), + content: DOM.div({className: "netInfoResponseContent"}, + sanitize(text) + ) + }; + } + + return group; + }, + + componentDidMount() { + let { actions, data: file } = this.props; + + if (!file.request.postData) { + // TODO: use async action objects as soon as Redux is in place + actions.requestData("requestPostData"); + } + }, + + render() { + let { actions, data: file } = this.props; + + if (file.discardRequestBody) { + return DOM.span({className: "netInfoBodiesDiscarded"}, + Locale.$STR("netRequest.requestBodyDiscarded") + ); + } + + if (!file.request.postData) { + return ( + Spinner() + ); + } + + // Render post body data. The right representation of the data + // is picked according to the content type. + let groups = []; + groups.push(this.renderUrlEncoded(file)); + // TODO: render multi part request (bug: 1247423) + // groups.push(this.renderMultiPart(file)); + groups.push(this.renderJson(file)); + groups.push(this.renderXml(file)); + groups.push(this.renderRawData(file)); + + // Filter out empty groups. + groups = groups.filter(group => group); + + // The raw response is collapsed by default if a nice formatted + // version is available. + if (groups.length > 1) { + groups[groups.length - 1].open = false; + } + + return ( + DOM.div({className: "postTabBox"}, + DOM.div({className: "panelContent"}, + NetInfoGroupList({ + groups: groups + }) + ) + ) + ); + } +}); + +// Helpers + +/** + * Workaround for a "not well-formed" error that react + * reports when there's multipart data passed to render. + */ +function sanitize(text) { + text = JSON.stringify(text); + text = text.replace(/\\r\\n/g, "\r\n").replace(/\\"/g, "\""); + return text.slice(1, text.length - 1); +} + +function isLongString(text) { + return typeof text == "object"; +} + +// Exports from this module +module.exports = PostTab; |