aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mitmproxy/web/app.py15
-rw-r--r--web/src/css/flowdetail.less2
-rw-r--r--web/src/js/components/ContentView.jsx81
-rw-r--r--web/src/js/components/ContentView/ContentEditor.jsx42
-rw-r--r--web/src/js/components/ContentView/ContentViews.jsx29
-rw-r--r--web/src/js/components/FlowView/Messages.jsx11
-rw-r--r--web/src/js/components/common/Button.jsx10
-rw-r--r--web/src/js/components/common/CodeEditor.jsx24
-rw-r--r--web/src/js/ducks/flows.js5
9 files changed, 130 insertions, 89 deletions
diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py
index b130a6f7..979d0467 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 edf97566..43078eff 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 6c9d9b26..fa26a057 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 {
// <Auto flow={flow} message={flow.request}/>
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 (
<div>
- {View.textView ? (
- <ContentLoader flow={flow} message={message}>
- <this.state.View update_content={content => flowsActions.update_content(this.props.flow, content)} content="" />
- </ContentLoader>
- ) : (
- <View flow={flow} update_content={content => flowsActions.update_content(this.props.flow, content)} message={message} />
- )}
- <div className="view-options text-center">
- <ViewSelector onSelectView={this.selectView} active={View} message={message}/>
- &nbsp;
- <a className="btn btn-default btn-xs" href={MessageUtils.getContentURL(flow, message)}>
- <i className="fa fa-download"/>
- </a>
- &nbsp;
- <a className="btn btn-default btn-xs" href="#" onClick={e => {this.fileInput.click(); e.preventDefault();}}>
- <i className="fa fa-upload"/>
- </a>
- <input
- ref={ref => this.fileInput = ref}
- className="hidden"
- type="file"
- onChange={e => this.onOpenFile(e)}
- />
+ <div className="row">
+ <div className="col-sm-12">
+ <ContentLoader flow={flow} message={message}>
+ <ContentEditor
+ onSave={this.props.onContentChange}
+ onClose={() => this.setState({contentEditorClosed : true})}
+ onOpen={() => this.setState({contentEditorClosed : false})}
+ isClosed={this.state.contentEditorClosed}
+ content=""
+ />
+ </ContentLoader>
+ </div>
</div>
+
+ {this.state.contentEditorClosed && (<div>
+ {View.textView ? (
+ <ContentLoader flow={flow} message={message}>
+ <this.state.View content="" />
+ </ContentLoader>
+ ) : (
+ <View flow={flow} message={message} />
+ )}
+
+
+
+ <div className="view-options text-center">
+ <ViewSelector onSelectView={this.selectView} active={View} message={message}/>
+ &nbsp;
+ <a className="btn btn-default btn-xs"
+ href={MessageUtils.getContentURL(flow, message)}
+ title="Download the content of the flow."
+ >
+ <i className="fa fa-download"/>
+ </a>
+ &nbsp;
+ <a className="btn btn-default btn-xs"
+ href="#"
+ onClick={e => {this.fileInput.click(); e.preventDefault();}}
+ title="Upload a file to replace the content."
+ >
+ <i className="fa fa-upload"/>
+ </a>
+ <input
+ ref={ref => this.fileInput = ref}
+ className="hidden"
+ type="file"
+ onChange={e => this.onOpenFile(e)}
+ />
+ </div>
+ </div>)}
</div>
)
}
diff --git a/web/src/js/components/ContentView/ContentEditor.jsx b/web/src/js/components/ContentView/ContentEditor.jsx
new file mode 100644
index 00000000..a38e4d6f
--- /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 (
+ <div>
+ {this.props.isClosed ?
+ <a className="btn btn-default btn-xs pull-right" onClick={this.props.onOpen}>
+ <i className="fa fa-pencil-square-o"/>
+ </a> :
+ <span>
+ <a className="btn btn-default btn-xs pull-right" onClick={this.props.onClose}>
+ <i className="fa fa-times"/>
+ </a>
+ <a className="btn btn-default btn-xs pull-right" onClick={() => this.props.onSave(this.state.content)}>
+ <i className="fa fa-floppy-o"/>
+ </a>
+ </span>
+ }
+ {!this.props.isClosed &&
+ <CodeEditor value={this.state.content} onChange={content => this.setState({content: content})}/>
+ }
+ </div>
+
+ )
+ }
+}
diff --git a/web/src/js/components/ContentView/ContentViews.jsx b/web/src/js/components/ContentView/ContentViews.jsx
index 617ed242..82ee0adc 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 (
- <CodeEditor value={content} onSave={update_content}/>
- )
+export function ViewRaw({ content }) {
+ return <pre>{content}</pre>
}
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 <ContentLoader message={message} flow={flow}><View update_content={update_content} content="" /></ContentLoader>
+ return <ContentLoader message={message} flow={flow}><View content="" /></ContentLoader>
} else {
return <View message={message} flow={flow} />
}
}
-ViewFile.matches = () => false
-
-ViewFile.propTypes = {
- message: React.PropTypes.object.isRequired,
- flow: React.PropTypes.object.isRequired,
-}
-
-export function ViewFile({ message, flow }) {
- return <div className="alert alert-info">
- {formatSize(message.contentLength)} content size.
- </div>
-}
-
export default views
diff --git a/web/src/js/components/FlowView/Messages.jsx b/web/src/js/components/FlowView/Messages.jsx
index d2c42a54..27e18c05 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 } })}
/>
<hr/>
- <ContentView flow={flow} message={flow.request} onChange={content => updateFlow({request: {content} })}/>
+ <ContentView flow={flow}
+ onContentChange={content => flowActions.updateContent(this.props.flow, content, "request") }
+ message={flow.request}
+ />
</section>
)
}
@@ -128,7 +132,10 @@ export class Response extends Component {
onChange={headers => updateFlow({ response: { headers } })}
/>
<hr/>
- <ContentView flow={flow} message={flow.response} onChange={content => updateFlow({response: {content} }) }/>
+ <ContentView flow={flow}
+ onContentChange={content => flowActions.updateContent(this.props.flow, content, "response") }
+ message={flow.response}
+ />
</section>
)
}
diff --git a/web/src/js/components/common/Button.jsx b/web/src/js/components/common/Button.jsx
index 221c6ace..cd01af22 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 }) {
<div className={"btn btn-default"}
onClick={onClick}
disabled={disabled}>
- <span hidden={!icon}>
- <i className={"fa fa-fw " + icon}/>
- &nbsp;
- </span>
- {text}
+ {icon && (<i className={"fa fa-fw " + icon}/> )}
+ {text && text}
</div>
)
}
diff --git a/web/src/js/components/common/CodeEditor.jsx b/web/src/js/components/common/CodeEditor.jsx
index 85da0507..b10b13ed 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 (
<div onKeyDown={e => e.stopPropagation()}>
<AceEditor
- onChange={e => 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}}
/>
- <Button onClick={(e) => this.props.onSave(this.state.value)} text="Update"/>
</div>
)
}
diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js
index 1dc88c67..3dd21016 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 }
}