From a56c2ca731ff9c4a22438553dea0e4ecf7a51f1d Mon Sep 17 00:00:00 2001 From: Clemens Date: Fri, 29 Jul 2016 09:45:15 +0200 Subject: [PATCH] combine clientside and serverside contentviews --- mitmproxy/web/app.py | 105 +++++++++++------- web/src/css/contentview.less | 12 +- .../components/ContentView/ContentViews.jsx | 32 ++---- .../components/ContentView/ViewSelector.jsx | 24 ++-- web/src/js/ducks/ui/flow.js | 4 + web/src/js/flow/utils.js | 2 +- 6 files changed, 93 insertions(+), 86 deletions(-) diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py index 6abd672da..eabdb1470 100644 --- a/mitmproxy/web/app.py +++ b/mitmproxy/web/app.py @@ -306,36 +306,6 @@ 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() @@ -369,21 +339,69 @@ 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 +class FlowContentView(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") - self.write(dict( - lines=list(lines), - description=description - )) - else: - self.write(message.raw_content) + if enc: + description = " ".join([enc, description]) + + return description, lines + + def get(self, flow_id, message, content_view): + message = getattr(self.flow, message) + + original_cd = message.headers.get("Content-Disposition", None) + filename = None + if original_cd: + filename = re.search("filename=([\w\" \.\-\(\)]+)", original_cd) + if filename: + filename = filename.group(1) + if not filename: + filename = self.flow.request.path.split("?")[0].split("/")[-1] + + filename = re.sub(r"[^\w\" \.\-\(\)]", "", filename) + cd = "attachment; filename={}".format(filename) + self.set_header("Content-Disposition", cd) + self.set_header("Content-Type", "application/json") + self.set_header("X-Content-Type-Options", "nosniff") + self.set_header("X-Frame-Options", "DENY") + + self.set_header("Content-Encoding", "") + + description, lines = self._get_content_view( + message, contentviews.get(content_view.replace('_', ' ')) + ) + + self.write(dict( + lines=list(lines), + description=description + )) class Events(RequestHandler): @@ -412,7 +430,7 @@ class Settings(RequestHandler): stickyauth=self.master.options.stickyauth, stickycookie=self.master.options.stickycookie, stream= self.master.options.stream_large_bodies, - contentViews= [v.name for v in contentviews.views] + contentViews= [v.name.replace(' ', '_') for v in contentviews.views] ) )) @@ -477,6 +495,7 @@ class Application(tornado.web.Application): (r"/flows/(?P[0-9a-f\-]+)/replay", ReplayFlow), (r"/flows/(?P[0-9a-f\-]+)/revert", RevertFlow), (r"/flows/(?P[0-9a-f\-]+)/(?Prequest|response)/content", FlowContent), + (r"/flows/(?P[0-9a-f\-]+)/(?Prequest|response)/content/(?P[0-9a-zA-Z\-\_]+)", FlowContentView), (r"/settings", Settings), (r"/clear", ClearAll), ] diff --git a/web/src/css/contentview.less b/web/src/css/contentview.less index 327dd6895..becac9a21 100644 --- a/web/src/css/contentview.less +++ b/web/src/css/contentview.less @@ -1,10 +1,14 @@ .contentview { .header { - font-weight: bold; + font-weight: bold; } .highlight{ - color: pink; + font-weight: bold; + } + .offset{ + color: blue + } + .text{ + } - .offset{ } - .text{ } } diff --git a/web/src/js/components/ContentView/ContentViews.jsx b/web/src/js/components/ContentView/ContentViews.jsx index a1bee54ef..3b2af0a90 100644 --- a/web/src/js/components/ContentView/ContentViews.jsx +++ b/web/src/js/components/ContentView/ContentViews.jsx @@ -18,31 +18,19 @@ function ViewImage({ flow, message }) { ) } - -ViewRaw.matches = () => true -ViewRaw.propTypes = { +Edit.propTypes = { content: React.PropTypes.string.isRequired, } -function ViewRaw({ content, readonly, onChange }) { - return readonly ?
{content}
: -} -ViewRaw = ContentLoader(ViewRaw) -ViewAuto.matches = () => false -ViewAuto.findView = msg => [ViewImage, ViewRaw].find(v => v.matches(msg)) || ViewRaw -ViewAuto.propTypes = { - message: React.PropTypes.object.isRequired, - flow: React.PropTypes.object.isRequired, -} -function ViewAuto({ message, flow, readonly, onChange }) { - const View = ViewAuto.findView(message) - return +function Edit({ content, onChange }) { + return } +Edit = ContentLoader(Edit) -function ViewServer({content, contentView, message, flow}){ +function ViewServer(props){ + const {content, contentView, message} = props let data = JSON.parse(content) - let showImage = isImage.test(MessageUtils.getContentType(message)) return
{contentView != data.description && @@ -59,14 +47,12 @@ function ViewServer({content, contentView, message, flow}){
)} - {showImage && -
- preview -
+ {ViewImage.matches(message) && + } } ViewServer = ContentLoader(ViewServer) -export { ViewImage, ViewRaw, ViewAuto, ViewServer } +export { Edit, ViewServer, ViewImage } diff --git a/web/src/js/components/ContentView/ViewSelector.jsx b/web/src/js/components/ContentView/ViewSelector.jsx index e031b51f5..c5670328b 100644 --- a/web/src/js/components/ContentView/ViewSelector.jsx +++ b/web/src/js/components/ContentView/ViewSelector.jsx @@ -24,25 +24,18 @@ ViewButton = connect(state => ({ ViewSelector.propTypes = { message: PropTypes.object.isRequired, } -function ViewSelector({ message, contentViews }) { - - let autoView = ContentViews.ViewAuto.findView(message) - let autoViewName = (autoView.displayName || autoView.name) - .toLowerCase() - .replace('view', '') - .replace(/ContentLoader\((.+)\)/,"$1") - +function ViewSelector({contentViews, isEdit }) { + let edit = ContentViews.Edit.displayName return (
- {Object.keys(ContentViews).map(name => - name === "ViewRaw" && - {name.toLowerCase().replace('view', '')} + {contentViews.map(name => + {name.toLowerCase().replace('_', ' ')} )} - {contentViews.map(name => - {name.toLowerCase().replace('view', '')} - )} + {isEdit && + {edit.toLowerCase()} + }
) @@ -50,5 +43,6 @@ function ViewSelector({ message, contentViews }) { export default connect ( state => ({ - contentViews: state.settings.contentViews + contentViews: state.settings.contentViews, + isEdit: !!state.ui.flow.modifiedFlow, }))(ViewSelector) diff --git a/web/src/js/ducks/ui/flow.js b/web/src/js/ducks/ui/flow.js index 549efb1d0..d9811a33c 100644 --- a/web/src/js/ducks/ui/flow.js +++ b/web/src/js/ducks/ui/flow.js @@ -19,12 +19,14 @@ const defaultState = { } export default function reducer(state = defaultState, action) { + let wasInEditMode = !!(state.modifiedFlow) switch (action.type) { case START_EDIT: return { ...state, modifiedFlow: action.flow, + contentView: 'Edit' } case UPDATE_EDIT: @@ -38,6 +40,7 @@ export default function reducer(state = defaultState, action) { ...state, modifiedFlow: false, displayLarge: false, + contentView: (wasInEditMode ? 'Auto' : state.contentView) } case flowsActions.UPDATE: @@ -49,6 +52,7 @@ export default function reducer(state = defaultState, action) { ...state, modifiedFlow: false, displayLarge: false, + contentView: (wasInEditMode ? 'Auto' : state.contentView) } } else { return state diff --git a/web/src/js/flow/utils.js b/web/src/js/flow/utils.js index b8435aa01..cd174069d 100644 --- a/web/src/js/flow/utils.js +++ b/web/src/js/flow/utils.js @@ -49,7 +49,7 @@ export var MessageUtils = { } else if (message === flow.response) { message = "response"; } - return `/flows/${flow.id}/${message}/content` + (view ? `?cv=${view}` : ''); + return `/flows/${flow.id}/${message}/content` + (view ? `/${view}` : ''); } };