post-tab.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. "use strict";
  5. const React = require("devtools/client/shared/vendor/react");
  6. // Reps
  7. const { createFactories, parseURLEncodedText } = require("devtools/client/shared/components/reps/rep-utils");
  8. const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
  9. const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
  10. // Network
  11. const NetInfoParams = React.createFactory(require("./net-info-params"));
  12. const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
  13. const Spinner = React.createFactory(require("./spinner"));
  14. const SizeLimit = React.createFactory(require("./size-limit"));
  15. const NetUtils = require("../utils/net");
  16. const Json = require("../utils/json");
  17. // Shortcuts
  18. const DOM = React.DOM;
  19. const PropTypes = React.PropTypes;
  20. /**
  21. * This template represents 'Post' tab displayed when the user
  22. * expands network log in the Console panel. It's responsible for
  23. * displaying posted data (HTTP post body).
  24. */
  25. var PostTab = React.createClass({
  26. propTypes: {
  27. data: PropTypes.shape({
  28. request: PropTypes.object.isRequired
  29. }),
  30. actions: PropTypes.object.isRequired
  31. },
  32. displayName: "PostTab",
  33. isJson(file) {
  34. let text = file.request.postData.text;
  35. let value = NetUtils.getHeaderValue(file.request.headers, "content-type");
  36. return Json.isJSON(value, text);
  37. },
  38. parseJson(file) {
  39. let postData = file.request.postData;
  40. if (!postData) {
  41. return null;
  42. }
  43. let jsonString = new String(postData.text);
  44. return Json.parseJSONString(jsonString);
  45. },
  46. /**
  47. * Render JSON post data as an expandable tree.
  48. */
  49. renderJson(file) {
  50. let text = file.request.postData.text;
  51. if (!text || isLongString(text)) {
  52. return null;
  53. }
  54. if (!this.isJson(file)) {
  55. return null;
  56. }
  57. let json = this.parseJson(file);
  58. if (!json) {
  59. return null;
  60. }
  61. return {
  62. key: "json",
  63. content: TreeView({
  64. columns: [{id: "value"}],
  65. object: json,
  66. mode: "tiny",
  67. renderValue: props => Rep(Object.assign({}, props, {
  68. cropLimit: 50,
  69. })),
  70. }),
  71. name: Locale.$STR("jsonScopeName")
  72. };
  73. },
  74. parseXml(file) {
  75. let text = file.request.postData.text;
  76. if (isLongString(text)) {
  77. return null;
  78. }
  79. return NetUtils.parseXml({
  80. mimeType: NetUtils.getHeaderValue(file.request.headers, "content-type"),
  81. text: text,
  82. });
  83. },
  84. isXml(file) {
  85. if (isLongString(file.request.postData.text)) {
  86. return false;
  87. }
  88. let value = NetUtils.getHeaderValue(file.request.headers, "content-type");
  89. if (!value) {
  90. return false;
  91. }
  92. return NetUtils.isHTML(value);
  93. },
  94. renderXml(file) {
  95. let text = file.request.postData.text;
  96. if (!text || isLongString(text)) {
  97. return null;
  98. }
  99. if (!this.isXml(file)) {
  100. return null;
  101. }
  102. let doc = this.parseXml(file);
  103. if (!doc) {
  104. return null;
  105. }
  106. // Proper component for rendering XML should be used (see bug 1247392)
  107. return null;
  108. },
  109. /**
  110. * Multipart post data are parsed and nicely rendered
  111. * as an expandable tree of individual parts.
  112. */
  113. renderMultiPart(file) {
  114. let text = file.request.postData.text;
  115. if (!text || isLongString(text)) {
  116. return;
  117. }
  118. if (NetUtils.isMultiPartRequest(file)) {
  119. // TODO: render multi part request (bug: 1247423)
  120. }
  121. return;
  122. },
  123. /**
  124. * URL encoded post data are nicely rendered as a list
  125. * of parameters.
  126. */
  127. renderUrlEncoded(file) {
  128. let text = file.request.postData.text;
  129. if (!text || isLongString(text)) {
  130. return null;
  131. }
  132. if (!NetUtils.isURLEncodedRequest(file)) {
  133. return null;
  134. }
  135. let lines = text.split("\n");
  136. let params = parseURLEncodedText(lines[lines.length - 1]);
  137. return {
  138. key: "url-encoded",
  139. content: NetInfoParams({params: params}),
  140. name: Locale.$STR("netRequest.params")
  141. };
  142. },
  143. renderRawData(file) {
  144. let text = file.request.postData.text;
  145. let group;
  146. // The post body might reached the limit, so check if we are
  147. // dealing with a long string.
  148. if (typeof text == "object") {
  149. group = {
  150. key: "raw-longstring",
  151. name: Locale.$STR("netRequest.rawData"),
  152. content: DOM.div({className: "netInfoResponseContent"},
  153. sanitize(text.initial),
  154. SizeLimit({
  155. actions: this.props.actions,
  156. data: file.request.postData,
  157. message: Locale.$STR("netRequest.sizeLimitMessage"),
  158. link: Locale.$STR("netRequest.sizeLimitMessageLink")
  159. })
  160. )
  161. };
  162. } else {
  163. group = {
  164. key: "raw",
  165. name: Locale.$STR("netRequest.rawData"),
  166. content: DOM.div({className: "netInfoResponseContent"},
  167. sanitize(text)
  168. )
  169. };
  170. }
  171. return group;
  172. },
  173. componentDidMount() {
  174. let { actions, data: file } = this.props;
  175. if (!file.request.postData) {
  176. // TODO: use async action objects as soon as Redux is in place
  177. actions.requestData("requestPostData");
  178. }
  179. },
  180. render() {
  181. let { actions, data: file } = this.props;
  182. if (file.discardRequestBody) {
  183. return DOM.span({className: "netInfoBodiesDiscarded"},
  184. Locale.$STR("netRequest.requestBodyDiscarded")
  185. );
  186. }
  187. if (!file.request.postData) {
  188. return (
  189. Spinner()
  190. );
  191. }
  192. // Render post body data. The right representation of the data
  193. // is picked according to the content type.
  194. let groups = [];
  195. groups.push(this.renderUrlEncoded(file));
  196. // TODO: render multi part request (bug: 1247423)
  197. // groups.push(this.renderMultiPart(file));
  198. groups.push(this.renderJson(file));
  199. groups.push(this.renderXml(file));
  200. groups.push(this.renderRawData(file));
  201. // Filter out empty groups.
  202. groups = groups.filter(group => group);
  203. // The raw response is collapsed by default if a nice formatted
  204. // version is available.
  205. if (groups.length > 1) {
  206. groups[groups.length - 1].open = false;
  207. }
  208. return (
  209. DOM.div({className: "postTabBox"},
  210. DOM.div({className: "panelContent"},
  211. NetInfoGroupList({
  212. groups: groups
  213. })
  214. )
  215. )
  216. );
  217. }
  218. });
  219. // Helpers
  220. /**
  221. * Workaround for a "not well-formed" error that react
  222. * reports when there's multipart data passed to render.
  223. */
  224. function sanitize(text) {
  225. text = JSON.stringify(text);
  226. text = text.replace(/\\r\\n/g, "\r\n").replace(/\\"/g, "\"");
  227. return text.slice(1, text.length - 1);
  228. }
  229. function isLongString(text) {
  230. return typeof text == "object";
  231. }
  232. // Exports from this module
  233. module.exports = PostTab;