diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py index b130a6f71..979d04675 100644 --- a/mitmproxy/web/app.py +++ b/mitmproxy/web/app.py @@ -201,7 +201,7 @@ class DumpFlows(RequestHandler): def post(self): self.state.clear() - content = self.request.files.values()[0][0]["body"] + content = self.request.files.values()[0][0].body bio = BytesIO(content) self.state.load_flows(FlowReader(bio).stream()) bio.close() @@ -297,14 +297,15 @@ class FlowContent(RequestHandler): flow = self.flow flow.backup() - content = 'Can not read file!' - if (len(self.request.files.values()) > 0): - content = self.request.files.values()[0][0]["body"] - flow.response.content = str(content) + content = self.request.files.values()[0][0].body + if (message == "response"): + with models.decoded(flow.response): + flow.response.content = content + elif(message == "request"): + with models.decoded(flow.request): + flow.request.content = content self.state.update_flow(flow) - - def get(self, flow_id, message): message = getattr(self.flow, message) diff --git a/web/src/css/flowdetail.less b/web/src/css/flowdetail.less index edf975662..43078eff9 100644 --- a/web/src/css/flowdetail.less +++ b/web/src/css/flowdetail.less @@ -109,4 +109,4 @@ text-overflow: ellipsis; white-space: nowrap; } -} \ No newline at end of file +} diff --git a/web/src/js/components/ContentView.jsx b/web/src/js/components/ContentView.jsx index 6c9d9b26a..fa26a057a 100644 --- a/web/src/js/components/ContentView.jsx +++ b/web/src/js/components/ContentView.jsx @@ -4,7 +4,7 @@ import { ViewAuto, ViewImage } from './ContentView/ContentViews' import * as MetaViews from './ContentView/MetaViews' import ContentLoader from './ContentView/ContentLoader' import ViewSelector from './ContentView/ViewSelector' -import * as flowsActions from '../ducks/flows' +import ContentEditor from './ContentView/ContentEditor' export default class ContentView extends Component { @@ -14,12 +14,13 @@ export default class ContentView extends Component { // flow: React.PropTypes.object.isRequired, message: React.PropTypes.object.isRequired, + onContentChange: React.PropTypes.func.isRequired } constructor(props, context) { super(props, context) - this.state = { displayLarge: false, View: ViewAuto } + this.state = { displayLarge: false, View: ViewAuto, contentEditorClosed: true } this.selectView = this.selectView.bind(this) } @@ -43,9 +44,7 @@ export default class ContentView extends Component { onOpenFile(e) { if (e.target.files.length > 0) { - //alert(e.target.files[0]) - flowsActions.update_content(this.props.flow, e.target.files[0]) - //this.fileInput.value = '' + this.props.onContentChange(e.target.files[0]) } e.preventDefault() } @@ -68,30 +67,56 @@ export default class ContentView extends Component { return (
- {View.textView ? ( - - flowsActions.update_content(this.props.flow, content)} content="" /> - - ) : ( - flowsActions.update_content(this.props.flow, content)} message={message} /> - )} -
- -   - - - -   - {this.fileInput.click(); e.preventDefault();}}> - - - this.fileInput = ref} - className="hidden" - type="file" - onChange={e => this.onOpenFile(e)} - /> +
+
+ + this.setState({contentEditorClosed : true})} + onOpen={() => this.setState({contentEditorClosed : false})} + isClosed={this.state.contentEditorClosed} + content="" + /> + +
+ + {this.state.contentEditorClosed && (
+ {View.textView ? ( + + + + ) : ( + + )} + + + +
+ +   + + + +   + {this.fileInput.click(); e.preventDefault();}} + title="Upload a file to replace the content." + > + + + this.fileInput = ref} + className="hidden" + type="file" + onChange={e => this.onOpenFile(e)} + /> +
+
)}
) } diff --git a/web/src/js/components/ContentView/ContentEditor.jsx b/web/src/js/components/ContentView/ContentEditor.jsx new file mode 100644 index 000000000..a38e4d6fc --- /dev/null +++ b/web/src/js/components/ContentView/ContentEditor.jsx @@ -0,0 +1,42 @@ +import React, { Component, PropTypes } from 'react' +import CodeEditor from '../common/CodeEditor' + +export default class ContentEditor extends Component { + + static propTypes = { + content: PropTypes.string.isRequired, + onSave: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + onOpen: PropTypes.func.isRequired, + isClosed: PropTypes.bool.isRequired + } + + constructor(props){ + super(props) + this.state = {content: this.props.content} + } + + render() { + return ( +
+ {this.props.isClosed ? + + + : + + + + + this.props.onSave(this.state.content)}> + + + + } + {!this.props.isClosed && + this.setState({content: content})}/> + } +
+ + ) + } +} diff --git a/web/src/js/components/ContentView/ContentViews.jsx b/web/src/js/components/ContentView/ContentViews.jsx index 617ed2422..82ee0adcc 100644 --- a/web/src/js/components/ContentView/ContentViews.jsx +++ b/web/src/js/components/ContentView/ContentViews.jsx @@ -1,12 +1,9 @@ import React, { PropTypes } from 'react' import ContentLoader from './ContentLoader' import { MessageUtils } from '../../flow/utils.js' -import CodeEditor from '../common/CodeEditor' -import {formatSize} from '../../utils.js' - -const views = [ViewAuto, ViewImage, ViewJSON, ViewRaw, ViewFile] +const views = [ViewAuto, ViewImage, ViewJSON, ViewRaw] ViewImage.regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i ViewImage.matches = msg => ViewImage.regex.test(MessageUtils.getContentType(msg)) @@ -26,16 +23,13 @@ export function ViewImage({ flow, message }) { ViewRaw.textView = true ViewRaw.matches = () => true -ViewRaw.input = {} ViewRaw.propTypes = { content: React.PropTypes.string.isRequired, } -export function ViewRaw({ content, update_content }) { - return ( - - ) +export function ViewRaw({ content }) { + return
{content}
} ViewJSON.textView = true @@ -65,26 +59,13 @@ ViewAuto.propTypes = { flow: React.PropTypes.object.isRequired, } -export function ViewAuto({ message, flow, update_content }) { +export function ViewAuto({ message, flow }) { const View = ViewAuto.findView(message) if (View.textView) { - return + return } else { return } } -ViewFile.matches = () => false - -ViewFile.propTypes = { - message: React.PropTypes.object.isRequired, - flow: React.PropTypes.object.isRequired, -} - -export function ViewFile({ message, flow }) { - return
- {formatSize(message.contentLength)} content size. -
-} - export default views diff --git a/web/src/js/components/FlowView/Messages.jsx b/web/src/js/components/FlowView/Messages.jsx index d2c42a54b..27e18c050 100644 --- a/web/src/js/components/FlowView/Messages.jsx +++ b/web/src/js/components/FlowView/Messages.jsx @@ -6,6 +6,7 @@ import { Key, formatTimeStamp } from '../../utils.js' import ContentView from '../ContentView' import ValueEditor from '../ValueEditor' import Headers from './Headers' +import * as flowActions from '../../ducks/flows' class RequestLine extends Component { @@ -89,7 +90,10 @@ export class Request extends Component { onChange={headers => updateFlow({ request: { headers } })} />
- updateFlow({request: {content} })}/> + flowActions.updateContent(this.props.flow, content, "request") } + message={flow.request} + /> ) } @@ -128,7 +132,10 @@ export class Response extends Component { onChange={headers => updateFlow({ response: { headers } })} />
- updateFlow({response: {content} }) }/> + flowActions.updateContent(this.props.flow, content, "response") } + message={flow.response} + /> ) } diff --git a/web/src/js/components/common/Button.jsx b/web/src/js/components/common/Button.jsx index 221c6ace5..cd01af227 100644 --- a/web/src/js/components/common/Button.jsx +++ b/web/src/js/components/common/Button.jsx @@ -2,7 +2,8 @@ import React, { PropTypes } from 'react' Button.propTypes = { onClick: PropTypes.func.isRequired, - text: PropTypes.string.isRequired + text: PropTypes.string, + icon: PropTypes.string } export default function Button({ onClick, text, icon, disabled }) { @@ -10,11 +11,8 @@ export default function Button({ onClick, text, icon, disabled }) {
- - {text} + {icon && ( )} + {text && text}
) } diff --git a/web/src/js/components/common/CodeEditor.jsx b/web/src/js/components/common/CodeEditor.jsx index 85da0507e..b10b13edf 100644 --- a/web/src/js/components/common/CodeEditor.jsx +++ b/web/src/js/components/common/CodeEditor.jsx @@ -1,40 +1,28 @@ import React, { Component, PropTypes } from 'react' import { render } from 'react-dom'; -import brace from 'brace'; import AceEditor from 'react-ace'; -import Button from './Button' - import 'brace/mode/javascript'; -import 'brace/mode/json'; import 'brace/theme/kuroir'; - - - export default class CodeEditor extends Component{ - constructor( props ) { - super(props) - this.state = {value: this.props.value} - } - - onChange(newValue) { - this.setState({value: newValue}) + static propTypes = { + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, } render() { return (
e.stopPropagation()}> this.onChange(e)} mode="javascript" theme="kuroir" - value={this.state.value} + onChange={this.props.onChange} + name="rea" + value={this.props.value} width="100%" - name="codeEditor" editorProps={{$blockScrolling: Infinity}} /> -
) } diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js index 1dc88c67a..3dd210163 100644 --- a/web/src/js/ducks/flows.js +++ b/web/src/js/ducks/flows.js @@ -117,13 +117,12 @@ export function update(flow, data) { return { type: REQUEST_ACTION } } -export function update_content(flow, file) { +export function updateContent(flow, file, type) { const body = new FormData() if (typeof file !== File) file = new Blob([file], {type: 'plain/text'}) body.append('file', file) - fetchApi(`/flows/${flow.id}/response/content`, {method: 'post', body} ) - update(flow, {response: {headers: [['Content-Encoding', '']]} } ) + fetchApi(`/flows/${flow.id}/${type}/content`, {method: 'post', body} ) return { type: REQUEST_ACTION } }