diff options
-rw-r--r-- | mitmproxy/web/app.py | 79 | ||||
-rw-r--r-- | web/src/css/contentview.less | 12 | ||||
-rw-r--r-- | web/src/js/components/ContentView/ContentViews.jsx | 32 | ||||
-rw-r--r-- | web/src/js/components/ContentView/ViewSelector.jsx | 24 | ||||
-rw-r--r-- | web/src/js/ducks/ui/flow.js | 4 | ||||
-rw-r--r-- | web/src/js/flow/utils.js | 2 |
6 files changed, 80 insertions, 73 deletions
diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py index 6abd672d..eabdb147 100644 --- a/mitmproxy/web/app.py +++ b/mitmproxy/web/app.py @@ -306,6 +306,42 @@ class ReplayFlow(RequestHandler): class FlowContent(RequestHandler): + + def post(self, flow_id, message): + self.flow.backup() + message = getattr(self.flow, message) + message.content = self.request.files.values()[0][0].body + self.state.update_flow(self.flow) + + def get(self, flow_id, message): + message = getattr(self.flow, message) + + if not message.raw_content: + raise APIError(400, "No content.") + + content_encoding = message.headers.get("Content-Encoding", None) + if content_encoding: + content_encoding = re.sub(r"[^\w]", "", content_encoding) + self.set_header("Content-Encoding", content_encoding) + + 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/text") + self.set_header("X-Content-Type-Options", "nosniff") + self.set_header("X-Frame-Options", "DENY") + self.write(message.raw_content) + +class FlowContentView(RequestHandler): def _get_content_view(self, message, viewmode): try: @@ -337,23 +373,9 @@ class FlowContent(RequestHandler): return description, lines - def post(self, flow_id, message): - self.flow.backup() - message = getattr(self.flow, message) - message.content = self.request.files.values()[0][0].body - self.state.update_flow(self.flow) - - def get(self, flow_id, message): + def get(self, flow_id, message, content_view): message = getattr(self.flow, message) - if not message.raw_content: - raise APIError(400, "No content.") - - content_encoding = message.headers.get("Content-Encoding", None) - if content_encoding: - content_encoding = re.sub(r"[^\w]", "", content_encoding) - self.set_header("Content-Encoding", content_encoding) - original_cd = message.headers.get("Content-Disposition", None) filename = None if original_cd: @@ -366,24 +388,20 @@ class FlowContent(RequestHandler): filename = re.sub(r"[^\w\" \.\-\(\)]", "", filename) cd = "attachment; filename={}".format(filename) self.set_header("Content-Disposition", cd) - self.set_header("Content-Type", "application/text") + self.set_header("Content-Type", "application/json") self.set_header("X-Content-Type-Options", "nosniff") self.set_header("X-Frame-Options", "DENY") - 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.set_header("Content-Encoding", "") - self.write(dict( - lines=list(lines), - description=description - )) - else: - self.write(message.raw_content) + 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<flow_id>[0-9a-f\-]+)/replay", ReplayFlow), (r"/flows/(?P<flow_id>[0-9a-f\-]+)/revert", RevertFlow), (r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content", FlowContent), + (r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content/(?P<content_view>[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 327dd689..becac9a2 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 a1bee54e..3b2af0a9 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 ? <pre>{content}</pre> : <CodeEditor content={content} onChange={onChange}/> -} -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 <View message={message} flow={flow} readonly={readonly} onChange={onChange}/> +function Edit({ content, onChange }) { + return <CodeEditor content={content} onChange={onChange}/> } +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 <div> {contentView != data.description && @@ -59,14 +47,12 @@ function ViewServer({content, contentView, message, flow}){ </div> )} </pre> - {showImage && - <div className="flowview-image"> - <img src={MessageUtils.getContentURL(flow, message)} alt="preview" className="img-thumbnail"/> - </div> + {ViewImage.matches(message) && + <ViewImage {...props} /> } </div> } 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 e031b51f..c5670328 100644 --- a/web/src/js/components/ContentView/ViewSelector.jsx +++ b/web/src/js/components/ContentView/ViewSelector.jsx @@ -24,31 +24,25 @@ 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 ( <div className="view-selector btn-group btn-group-xs"> - {Object.keys(ContentViews).map(name => - name === "ViewRaw" && - <ViewButton key={name} name={name}>{name.toLowerCase().replace('view', '')}</ViewButton> - )} - {contentViews.map(name => - <ViewButton key={name} name={name}>{name.toLowerCase().replace('view', '')}</ViewButton> + <ViewButton key={name} name={name}>{name.toLowerCase().replace('_', ' ')}</ViewButton> )} + {isEdit && + <ViewButton key={edit} name={edit}>{edit.toLowerCase()}</ViewButton> + } + </div> ) } 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 549efb1d..d9811a33 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 b8435aa0..cd174069 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}` : ''); } }; |