aboutsummaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2015-03-27 15:30:19 +0100
committerMaximilian Hils <git@maximilianhils.com>2015-03-27 15:30:19 +0100
commit42cd942b64e53db9feb5f6c8b2a95669e97b1230 (patch)
treec0b2714a103ad57a9b9c21cf4d4b4c834e77df0d /web
parent04f2467e84905d44483469cf43ada4ec44faa203 (diff)
downloadmitmproxy-42cd942b64e53db9feb5f6c8b2a95669e97b1230.tar.gz
mitmproxy-42cd942b64e53db9feb5f6c8b2a95669e97b1230.tar.bz2
mitmproxy-42cd942b64e53db9feb5f6c8b2a95669e97b1230.zip
web: initial attempt at header editor
Diffstat (limited to 'web')
-rw-r--r--web/src/js/components/flowview/messages.js154
-rw-r--r--web/src/js/flow/utils.js3
2 files changed, 134 insertions, 23 deletions
diff --git a/web/src/js/components/flowview/messages.js b/web/src/js/components/flowview/messages.js
index cb8516fc..7feaa634 100644
--- a/web/src/js/components/flowview/messages.js
+++ b/web/src/js/components/flowview/messages.js
@@ -8,15 +8,77 @@ var utils = require("../../utils.js");
var ContentView = require("./contentview.js");
var Headers = React.createClass({
+ propTypes: {
+ onChange: React.PropTypes.func.isRequired,
+ message: React.PropTypes.object.isRequired
+ },
+ onChange: function (row, col, val) {
+ var nextHeaders = _.cloneDeep(this.props.message.headers);
+ nextHeaders[row][col] = val;
+ if (!nextHeaders[row][0] && !nextHeaders[row][1]) {
+ // do not delete last row
+ if (nextHeaders.length === 1) {
+ nextHeaders[0][0] = "Name";
+ nextHeaders[0][1] = "Value";
+ } else {
+ nextHeaders.splice(row, 1);
+ // manually move selection target if this has been the last row.
+ if(row === nextHeaders.length){
+ this._nextSel = (row-1)+"-value";
+ }
+ }
+ }
+ this.props.onChange(nextHeaders);
+ },
+ onTab: function (row, col, e) {
+ var headers = this.props.message.headers;
+ if (row === headers.length - 1 && col === 1) {
+ e.preventDefault();
+
+ var nextHeaders = _.cloneDeep(this.props.message.headers);
+ nextHeaders.push(["Name", "Value"]);
+ this.props.onChange(nextHeaders);
+ this._nextSel = (row + 1) + "-key";
+ }
+ },
+ componentDidUpdate: function () {
+ if (this._nextSel && this.refs[this._nextSel]) {
+ this.refs[this._nextSel].focus();
+ this._nextSel = undefined;
+ }
+ },
+ onRemove: function (row, col, e) {
+ if (col === 1) {
+ e.preventDefault();
+ this.refs[row + "-key"].focus();
+ } else if (row > 0) {
+ e.preventDefault();
+ this.refs[(row - 1) + "-value"].focus();
+ }
+ },
render: function () {
+
var rows = this.props.message.headers.map(function (header, i) {
+
+ var kEdit = <HeaderInlineInput
+ ref={i + "-key"}
+ content={header[0]}
+ onChange={this.onChange.bind(null, i, 0)}
+ onRemove={this.onRemove.bind(null, i, 0)}
+ onTab={this.onTab.bind(null, i, 0)}/>;
+ var vEdit = <HeaderInlineInput
+ ref={i + "-value"}
+ content={header[1]}
+ onChange={this.onChange.bind(null, i, 1)}
+ onRemove={this.onRemove.bind(null, i, 1)}
+ onTab={this.onTab.bind(null, i, 1)}/>;
return (
<tr key={i}>
- <td className="header-name">{header[0] + ":"}</td>
- <td className="header-value">{header[1]}</td>
+ <td className="header-name">{kEdit}:</td>
+ <td className="header-value">{vEdit}</td>
</tr>
);
- });
+ }.bind(this));
return (
<table className="header-table">
<tbody>
@@ -27,8 +89,13 @@ var Headers = React.createClass({
}
});
+
var InlineInput = React.createClass({
mixins: [common.ChildFocus],
+ propTypes: {
+ content: React.PropTypes.string.isRequired, //must be string to match strict equality.
+ onChange: React.PropTypes.func.isRequired,
+ },
getInitialState: function () {
return {
editable: false
@@ -65,10 +132,11 @@ var InlineInput = React.createClass({
}
break;
default:
+ this.props.onKeyDown && this.props.onKeyDown(e);
break;
}
},
- blur: function(){
+ blur: function () {
this.getDOMNode().blur();
this.context.returnFocus && this.context.returnFocus();
},
@@ -98,17 +166,49 @@ var InlineInput = React.createClass({
}
});
+var HeaderInlineInput = React.createClass({
+ render: function () {
+ return <InlineInput ref="input" {...this.props} onKeyDown={this.onKeyDown}/>;
+ },
+ focus: function () {
+ this.getDOMNode().focus();
+ },
+ onKeyDown: function (e) {
+ switch (e.keyCode) {
+ case utils.Key.BACKSPACE:
+ var s = window.getSelection().getRangeAt(0);
+ if (s.startOffset === 0 && s.endOffset === 0) {
+ this.props.onRemove(e);
+ }
+ break;
+ case utils.Key.TAB:
+ if(!e.shiftKey){
+ this.props.onTab(e);
+ }
+ break;
+ }
+ }
+});
+
var ValidateInlineInput = React.createClass({
+ propTypes: {
+ onChange: React.PropTypes.func.isRequired,
+ isValid: React.PropTypes.func.isRequired,
+ immediate: React.PropTypes.bool
+ },
getInitialState: function () {
return {
- content: ""+this.props.content,
- originalContent: ""+this.props.content
+ content: this.props.content,
+ originalContent: this.props.content
};
},
onChange: function (val) {
this.setState({
content: val
});
+ if (this.props.immediate && val !== this.state.originalContent && this.props.isValid(val)) {
+ this.props.onChange(val);
+ }
},
onDone: function () {
if (this.state.content === this.state.originalContent) {
@@ -125,8 +225,8 @@ var ValidateInlineInput = React.createClass({
componentWillReceiveProps: function (nextProps) {
if (nextProps.content !== this.state.content) {
this.setState({
- content: ""+nextProps.content,
- originalContent: ""+nextProps.content
+ content: nextProps.content,
+ originalContent: nextProps.content
})
}
},
@@ -153,16 +253,13 @@ var RequestLine = React.createClass({
var httpver = "HTTP/" + flow.request.httpversion.join(".");
return <div className="first-line request-line">
- <ValidateInlineInput content={flow.request.method} onChange={this.onMethodChange} isValid={this.isValidMethod}/>
+ <InlineInput content={flow.request.method} onChange={this.onMethodChange}/>
&nbsp;
<ValidateInlineInput content={url} onChange={this.onUrlChange} isValid={this.isValidUrl} />
&nbsp;
- <ValidateInlineInput content={httpver} onChange={this.onHttpVersionChange} isValid={flowutils.isValidHttpVersion} />
+ <ValidateInlineInput immediate content={httpver} onChange={this.onHttpVersionChange} isValid={flowutils.isValidHttpVersion} />
</div>
},
- isValidMethod: function (method) {
- return true;
- },
isValidUrl: function (url) {
var u = flowutils.parseUrl(url);
return !!u.host;
@@ -195,20 +292,17 @@ var ResponseLine = React.createClass({
var flow = this.props.flow;
var httpver = "HTTP/" + flow.response.httpversion.join(".");
return <div className="first-line response-line">
- <ValidateInlineInput content={httpver} onChange={this.onHttpVersionChange} isValid={flowutils.isValidHttpVersion} />
+ <ValidateInlineInput immediate content={httpver} onChange={this.onHttpVersionChange} isValid={flowutils.isValidHttpVersion} />
&nbsp;
- <ValidateInlineInput content={flow.response.code} onChange={this.onCodeChange} isValid={this.isValidCode} />
+ <ValidateInlineInput immediate content={flow.response.code + ""} onChange={this.onCodeChange} isValid={this.isValidCode} />
&nbsp;
- <ValidateInlineInput content={flow.response.msg} onChange={this.onMsgChange} isValid={this.isValidMsg} />
+ <InlineInput content={flow.response.msg} onChange={this.onMsgChange}/>
</div>;
},
isValidCode: function (code) {
return /^\d+$/.test(code);
},
- isValidMsg: function () {
- return true;
- },
onHttpVersionChange: function (nextVer) {
var ver = flowutils.parseHttpVersion(nextVer);
actions.FlowActions.update(
@@ -216,13 +310,13 @@ var ResponseLine = React.createClass({
{response: {httpversion: ver}}
);
},
- onMsgChange: function(nextMsg){
+ onMsgChange: function (nextMsg) {
actions.FlowActions.update(
this.props.flow,
{response: {msg: nextMsg}}
);
},
- onCodeChange: function(nextCode){
+ onCodeChange: function (nextCode) {
nextCode = parseInt(nextCode);
actions.FlowActions.update(
this.props.flow,
@@ -238,11 +332,18 @@ var Request = React.createClass({
<section className="request">
<RequestLine flow={flow}/>
{/*<ResponseLine flow={flow}/>*/}
- <Headers message={flow.request}/>
+ <Headers message={flow.request} onChange={this.onHeaderChange}/>
<hr/>
<ContentView flow={flow} message={flow.request}/>
</section>
);
+ },
+ onHeaderChange: function (nextHeaders) {
+ actions.FlowActions.update(this.props.flow, {
+ request: {
+ headers: nextHeaders
+ }
+ });
}
});
@@ -253,11 +354,18 @@ var Response = React.createClass({
<section className="response">
{/*<RequestLine flow={flow}/>*/}
<ResponseLine flow={flow}/>
- <Headers message={flow.response}/>
+ <Headers message={flow.response} onChange={this.onHeaderChange}/>
<hr/>
<ContentView flow={flow} message={flow.response}/>
</section>
);
+ },
+ onHeaderChange: function (nextHeaders) {
+ actions.FlowActions.update(this.props.flow, {
+ response: {
+ headers: nextHeaders
+ }
+ });
}
});
diff --git a/web/src/js/flow/utils.js b/web/src/js/flow/utils.js
index aa91e3b6..d72febaa 100644
--- a/web/src/js/flow/utils.js
+++ b/web/src/js/flow/utils.js
@@ -80,6 +80,9 @@ var parseUrl = function (url) {
//there are many correct ways to parse a URL,
//however, a mitmproxy user may also wish to generate a not-so-correct URL. ;-)
var parts = parseUrl_regex.exec(url);
+ if(!parts){
+ return false;
+ }
var scheme = parts[1],
host = parts[2],