diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py index 5f756ed1d..6abd672da 100644 --- a/mitmproxy/web/app.py +++ b/mitmproxy/web/app.py @@ -14,6 +14,7 @@ import tornado.web from io import BytesIO from mitmproxy.flow import FlowWriter, FlowReader +from mitmproxy import exceptions from mitmproxy import filt from mitmproxy import models from mitmproxy import contentviews @@ -305,6 +306,36 @@ class ReplayFlow(RequestHandler): class FlowContent(RequestHandler): + def _get_content_view(self, message, viewmode): + + try: + content = message.content + if content != message.raw_content: + enc = "[decoded {}]".format( + message.headers.get("content-encoding") + ) + else: + enc = None + except ValueError: + content = message.raw_content + enc = "[cannot decode]" + try: + query = None + if isinstance(message, models.HTTPRequest): + query = message.query + description, lines = contentviews.get_content_view( + viewmode, content, headers=message.headers, query=query + ) + except exceptions.ContentViewException: + description, lines = contentviews.get_content_view( + contentviews.get("Raw"), content, headers=message.headers + ) + description = description.replace("Raw", "Couldn't parse: falling back to Raw") + + if enc: + description = " ".join([enc, description]) + + return description, lines def post(self, flow_id, message): self.flow.backup() @@ -314,7 +345,6 @@ class FlowContent(RequestHandler): def get(self, flow_id, message): message = getattr(self.flow, message) - contentview = self.get_argument("content_view", "raw", True) if not message.raw_content: raise APIError(400, "No content.") @@ -339,7 +369,21 @@ class FlowContent(RequestHandler): self.set_header("Content-Type", "application/text") self.set_header("X-Content-Type-Options", "nosniff") self.set_header("X-Frame-Options", "DENY") - self.write(message.raw_content) + + cv = self.get_argument("cv", None) + if cv: + self.set_header("Content-Encoding", "") + viewmode = next(v for v in contentviews.views if v.name == cv) + description, lines = self._get_content_view( + message, viewmode + ) + + self.write(dict( + lines=list(lines), + description=description + )) + else: + self.write(message.raw_content) class Events(RequestHandler): @@ -368,7 +412,7 @@ class Settings(RequestHandler): stickyauth=self.master.options.stickyauth, stickycookie=self.master.options.stickycookie, stream= self.master.options.stream_large_bodies, - contentViews= map(lambda v : v.name, contentviews.views) + contentViews= [v.name for v in contentviews.views] ) )) diff --git a/web/src/css/app.less b/web/src/css/app.less index 6f27f4474..353e412a4 100644 --- a/web/src/css/app.less +++ b/web/src/css/app.less @@ -18,3 +18,4 @@ html { @import (less) "eventlog.less"; @import (less) "footer.less"; @import (less) "codemirror.less"; +@import (less) "contentview.less"; diff --git a/web/src/css/contentview.less b/web/src/css/contentview.less new file mode 100644 index 000000000..327dd6895 --- /dev/null +++ b/web/src/css/contentview.less @@ -0,0 +1,10 @@ +.contentview { + .header { + font-weight: bold; + } + .highlight{ + color: pink; + } + .offset{ } + .text{ } +} diff --git a/web/src/js/components/ContentView.jsx b/web/src/js/components/ContentView.jsx index a93ce3950..de4ffd063 100644 --- a/web/src/js/components/ContentView.jsx +++ b/web/src/js/components/ContentView.jsx @@ -35,7 +35,7 @@ function ContentView(props) { const View = ContentViews[contentView] || ContentViews['ViewServer'] return ( -
+
diff --git a/web/src/js/components/ContentView/ContentLoader.jsx b/web/src/js/components/ContentView/ContentLoader.jsx index 9babb8f79..e7a6f379f 100644 --- a/web/src/js/components/ContentView/ContentLoader.jsx +++ b/web/src/js/components/ContentView/ContentLoader.jsx @@ -53,7 +53,7 @@ export default View => class extends React.Component { return this.setState({request: undefined, content: ""}) } - let requestUrl = MessageUtils.getContentURL(props.flow, props.message, props.contentView) + let requestUrl = MessageUtils.getContentURL(props.flow, props.message, (View.name == 'ViewServer' ? props.contentView : undefined)) // We use XMLHttpRequest instead of fetch() because fetch() is not (yet) abortable. let request = new XMLHttpRequest(); diff --git a/web/src/js/components/ContentView/ContentViews.jsx b/web/src/js/components/ContentView/ContentViews.jsx index 732f9f5e8..89e97267b 100644 --- a/web/src/js/components/ContentView/ContentViews.jsx +++ b/web/src/js/components/ContentView/ContentViews.jsx @@ -28,26 +28,8 @@ function ViewRaw({ content, readonly, onChange }) { } ViewRaw = ContentLoader(ViewRaw) - -const isJSON = /^application\/json$/i -ViewJSON.matches = msg => isJSON.test(MessageUtils.getContentType(msg)) -ViewJSON.propTypes = { - content: React.PropTypes.string.isRequired, -} -function ViewJSON({ content }) { - let json = content - try { - json = JSON.stringify(JSON.parse(content), null, 2); - } catch (e) { - // @noop - } - return
{json}
-} -ViewJSON = ContentLoader(ViewJSON) - - ViewAuto.matches = () => false -ViewAuto.findView = msg => [ViewImage, ViewJSON, ViewRaw].find(v => v.matches(msg)) || ViewRaw +ViewAuto.findView = msg => [ViewImage, ViewRaw].find(v => v.matches(msg)) || ViewRaw ViewAuto.propTypes = { message: React.PropTypes.object.isRequired, flow: React.PropTypes.object.isRequired, @@ -57,14 +39,26 @@ function ViewAuto({ message, flow, readonly, onChange }) { return } -function ViewServer({contentView, content}){ +function ViewServer({content, contentView}){ + let data = JSON.parse(content) return
-
load from server this view: {contentView}
-
{content}
+ {contentView != data.description && +
{data.description}
+ } +
+                {data.lines.map((line, i) =>
+                    
+ {line.map((tuple, j) => + + {tuple[1]} + + )} +
+ )} +
- } ViewServer = ContentLoader(ViewServer) -export { ViewImage, ViewRaw, ViewAuto, ViewJSON, ViewServer } +export { ViewImage, ViewRaw, ViewAuto, ViewServer } diff --git a/web/src/js/components/ContentView/ViewSelector.jsx b/web/src/js/components/ContentView/ViewSelector.jsx index c3e1e1059..423cc157d 100644 --- a/web/src/js/components/ContentView/ViewSelector.jsx +++ b/web/src/js/components/ContentView/ViewSelector.jsx @@ -38,7 +38,7 @@ function ViewSelector({ message, contentViews }) { auto: {autoViewName} {Object.keys(ContentViews).map(name => - name !== "ViewAuto" && + name !== "ViewAuto" && name !== "ViewServer" && {name.toLowerCase().replace('view', '')} )} diff --git a/web/src/js/flow/utils.js b/web/src/js/flow/utils.js index 0232e7533..b8435aa01 100644 --- a/web/src/js/flow/utils.js +++ b/web/src/js/flow/utils.js @@ -49,8 +49,8 @@ export var MessageUtils = { } else if (message === flow.response) { message = "response"; } - return "/flows/" + flow.id + "/" + message + "/content" + (view ? "?content_view="+view : ""); - }, + return `/flows/${flow.id}/${message}/content` + (view ? `?cv=${view}` : ''); + } }; export var RequestUtils = _.extend(MessageUtils, {