mitmproxy.contentviews added

This commit is contained in:
Clemens 2016-07-28 17:10:06 +02:00
parent 3e6c284757
commit ad5bebeda0
8 changed files with 81 additions and 32 deletions

View File

@ -14,6 +14,7 @@ import tornado.web
from io import BytesIO from io import BytesIO
from mitmproxy.flow import FlowWriter, FlowReader from mitmproxy.flow import FlowWriter, FlowReader
from mitmproxy import exceptions
from mitmproxy import filt from mitmproxy import filt
from mitmproxy import models from mitmproxy import models
from mitmproxy import contentviews from mitmproxy import contentviews
@ -305,6 +306,36 @@ class ReplayFlow(RequestHandler):
class FlowContent(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): def post(self, flow_id, message):
self.flow.backup() self.flow.backup()
@ -314,7 +345,6 @@ class FlowContent(RequestHandler):
def get(self, flow_id, message): def get(self, flow_id, message):
message = getattr(self.flow, message) message = getattr(self.flow, message)
contentview = self.get_argument("content_view", "raw", True)
if not message.raw_content: if not message.raw_content:
raise APIError(400, "No content.") raise APIError(400, "No content.")
@ -339,7 +369,21 @@ class FlowContent(RequestHandler):
self.set_header("Content-Type", "application/text") self.set_header("Content-Type", "application/text")
self.set_header("X-Content-Type-Options", "nosniff") self.set_header("X-Content-Type-Options", "nosniff")
self.set_header("X-Frame-Options", "DENY") 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
)
self.write(dict(
lines=list(lines),
description=description
))
else:
self.write(message.raw_content)
class Events(RequestHandler): class Events(RequestHandler):
@ -368,7 +412,7 @@ class Settings(RequestHandler):
stickyauth=self.master.options.stickyauth, stickyauth=self.master.options.stickyauth,
stickycookie=self.master.options.stickycookie, stickycookie=self.master.options.stickycookie,
stream= self.master.options.stream_large_bodies, stream= self.master.options.stream_large_bodies,
contentViews= map(lambda v : v.name, contentviews.views) contentViews= [v.name for v in contentviews.views]
) )
)) ))

View File

@ -18,3 +18,4 @@ html {
@import (less) "eventlog.less"; @import (less) "eventlog.less";
@import (less) "footer.less"; @import (less) "footer.less";
@import (less) "codemirror.less"; @import (less) "codemirror.less";
@import (less) "contentview.less";

View File

@ -0,0 +1,10 @@
.contentview {
.header {
font-weight: bold;
}
.highlight{
color: pink;
}
.offset{ }
.text{ }
}

View File

@ -35,7 +35,7 @@ function ContentView(props) {
const View = ContentViews[contentView] || ContentViews['ViewServer'] const View = ContentViews[contentView] || ContentViews['ViewServer']
return ( return (
<div> <div className="contentview">
<View flow={flow} message={message} contentView={contentView} readonly={readonly} onChange={onContentChange}/> <View flow={flow} message={message} contentView={contentView} readonly={readonly} onChange={onContentChange}/>
<div className="view-options text-center"> <div className="view-options text-center">

View File

@ -53,7 +53,7 @@ export default View => class extends React.Component {
return this.setState({request: undefined, content: ""}) return this.setState({request: undefined, content: ""})
} }
let requestUrl = MessageUtils.getContentURL(props.flow, props.message, props.contentView) let requestUrl = MessageUtils.getContentURL(props.flow, props.message, (View.name == 'ViewServer' ? props.contentView : undefined))
// We use XMLHttpRequest instead of fetch() because fetch() is not (yet) abortable. // We use XMLHttpRequest instead of fetch() because fetch() is not (yet) abortable.
let request = new XMLHttpRequest(); let request = new XMLHttpRequest();

View File

@ -28,26 +28,8 @@ function ViewRaw({ content, readonly, onChange }) {
} }
ViewRaw = ContentLoader(ViewRaw) ViewRaw = ContentLoader(ViewRaw)
const isJSON = /^application\/json$/i
ViewJSON.matches = msg => isJSON.test(MessageUtils.getContentType(msg))
ViewJSON.propTypes = {
content: React.PropTypes.string.isRequired,
}
function ViewJSON({ content }) {
let json = content
try {
json = JSON.stringify(JSON.parse(content), null, 2);
} catch (e) {
// @noop
}
return <pre>{json}</pre>
}
ViewJSON = ContentLoader(ViewJSON)
ViewAuto.matches = () => false ViewAuto.matches = () => false
ViewAuto.findView = msg => [ViewImage, ViewJSON, ViewRaw].find(v => v.matches(msg)) || ViewRaw ViewAuto.findView = msg => [ViewImage, ViewRaw].find(v => v.matches(msg)) || ViewRaw
ViewAuto.propTypes = { ViewAuto.propTypes = {
message: React.PropTypes.object.isRequired, message: React.PropTypes.object.isRequired,
flow: React.PropTypes.object.isRequired, flow: React.PropTypes.object.isRequired,
@ -57,14 +39,26 @@ function ViewAuto({ message, flow, readonly, onChange }) {
return <View message={message} flow={flow} readonly={readonly} onChange={onChange}/> return <View message={message} flow={flow} readonly={readonly} onChange={onChange}/>
} }
function ViewServer({contentView, content}){ function ViewServer({content, contentView}){
let data = JSON.parse(content)
return <div> return <div>
<pre>load from server this view: {contentView}</pre> {contentView != data.description &&
<pre>{content}</pre> <div className="alert alert-warning">{data.description}</div>
}
<pre>
{data.lines.map((line, i) =>
<div key={`line${i}`}>
{line.map((tuple, j) =>
<span key={`tuple${j}`} className={tuple[0]}>
{tuple[1]}
</span>
)}
</div>
)}
</pre>
</div> </div>
} }
ViewServer = ContentLoader(ViewServer) ViewServer = ContentLoader(ViewServer)
export { ViewImage, ViewRaw, ViewAuto, ViewJSON, ViewServer } export { ViewImage, ViewRaw, ViewAuto, ViewServer }

View File

@ -38,7 +38,7 @@ function ViewSelector({ message, contentViews }) {
<ViewButton name="ViewAuto">auto: {autoViewName}</ViewButton> <ViewButton name="ViewAuto">auto: {autoViewName}</ViewButton>
{Object.keys(ContentViews).map(name => {Object.keys(ContentViews).map(name =>
name !== "ViewAuto" && name !== "ViewAuto" && name !== "ViewServer" &&
<ViewButton key={name} name={name}>{name.toLowerCase().replace('view', '')}</ViewButton> <ViewButton key={name} name={name}>{name.toLowerCase().replace('view', '')}</ViewButton>
)} )}

View File

@ -49,8 +49,8 @@ export var MessageUtils = {
} else if (message === flow.response) { } else if (message === flow.response) {
message = "response"; message = "response";
} }
return "/flows/" + flow.id + "/" + message + "/content" + (view ? "?content_view="+view : ""); return `/flows/${flow.id}/${message}/content` + (view ? `?cv=${view}` : '');
}, }
}; };
export var RequestUtils = _.extend(MessageUtils, { export var RequestUtils = _.extend(MessageUtils, {