Add flow.comment command and keybinding to add a comment to a flow. (#4608)

* Add `flow.comment` command and keybinding to add a comment to a flow.

* Store comment in Flow().comment. Add ~comment flowfilter syntax.

* resolve: Pythonic flow.comment

* Be consistent and use comment variable.
This commit is contained in:
Brad Dixon 2021-05-28 14:38:27 -04:00 committed by GitHub
parent 4f60e52413
commit 6d2b823a54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 64 additions and 1 deletions

View File

@ -66,6 +66,7 @@ If you depend on these features, please raise your voice in
* Fix parsing of certificate issuer/subject with escaped special characters (@Prinzhorn) * Fix parsing of certificate issuer/subject with escaped special characters (@Prinzhorn)
* Customize markers with emoji, and filters: The `flow.mark` command may be used to mark a flow with either the default * Customize markers with emoji, and filters: The `flow.mark` command may be used to mark a flow with either the default
"red ball" marker, a single character, or an emoji like `:grapes:`. Use the `~marker` filter to filter on marker characters. (@rbdixon) "red ball" marker, a single character, or an emoji like `:grapes:`. Use the `~marker` filter to filter on marker characters. (@rbdixon)
* New `flow.comment` command to add a comment to the flow. Add `~comment <regex>` filter syntax to search flow comments. (@rbdixon)
* --- TODO: add new PRs above this line --- * --- TODO: add new PRs above this line ---
* ... and various other fixes, documentation improvements, dependency version bumps, etc. * ... and various other fixes, documentation improvements, dependency version bumps, etc.

View File

@ -5,6 +5,7 @@ from mitmproxy.addons import blocklist
from mitmproxy.addons import browser from mitmproxy.addons import browser
from mitmproxy.addons import clientplayback from mitmproxy.addons import clientplayback
from mitmproxy.addons import command_history from mitmproxy.addons import command_history
from mitmproxy.addons import comment
from mitmproxy.addons import core from mitmproxy.addons import core
from mitmproxy.addons import cut from mitmproxy.addons import cut
from mitmproxy.addons import disable_h2c from mitmproxy.addons import disable_h2c
@ -37,6 +38,7 @@ def default_addons():
anticomp.AntiComp(), anticomp.AntiComp(),
clientplayback.ClientPlayback(), clientplayback.ClientPlayback(),
command_history.CommandHistory(), command_history.CommandHistory(),
comment.Comment(),
cut.Cut(), cut.Cut(),
disable_h2c.DisableH2C(), disable_h2c.DisableH2C(),
export.Export(), export.Export(),

View File

@ -0,0 +1,16 @@
import typing
from mitmproxy import command, flow, ctx
from mitmproxy.hooks import UpdateHook
class Comment:
@command.command("flow.comment")
def comment(self, flow: typing.Sequence[flow.Flow], comment: str) -> None:
"Add a comment to a flow"
updated = []
for f in flow:
f.comment = comment
updated.append(f)
ctx.master.addons.trigger(UpdateHook(updated))

View File

@ -123,6 +123,7 @@ class Flow(stateobject.StateObject):
self.marked: str = "" self.marked: str = ""
self.is_replay: typing.Optional[str] = None self.is_replay: typing.Optional[str] = None
self.metadata: typing.Dict[str, typing.Any] = dict() self.metadata: typing.Dict[str, typing.Any] = dict()
self.comment: str = ""
_stateobject_attributes = dict( _stateobject_attributes = dict(
id=str, id=str,
@ -134,6 +135,7 @@ class Flow(stateobject.StateObject):
is_replay=str, is_replay=str,
marked=str, marked=str,
metadata=typing.Dict[str, typing.Any], metadata=typing.Dict[str, typing.Any],
comment=str,
) )
def get_state(self): def get_state(self):

View File

@ -425,6 +425,16 @@ class FMarker(_Rex):
return self.re.search(f.marked) return self.re.search(f.marked)
class FComment(_Rex):
code = "comment"
help = "Flow comment"
flags = re.MULTILINE
is_binary = False
def __call__(self, f):
return self.re.search(f.comment)
class _Int(_Action): class _Int(_Action):
def __init__(self, num): def __init__(self, num):
@ -512,6 +522,7 @@ filter_rex: Sequence[Type[_Rex]] = [
FUrl, FUrl,
FMeta, FMeta,
FMarker, FMarker,
FComment,
] ]
filter_int = [ filter_int = [
FCode FCode

View File

@ -309,6 +309,12 @@ def convert_12_13(data):
return data return data
def convert_13_14(data):
data["version"] = 14
data["comment"] = ""
return data
def _convert_dict_keys(o: Any) -> Any: def _convert_dict_keys(o: Any) -> Any:
if isinstance(o, dict): if isinstance(o, dict):
return {strutils.always_str(k): _convert_dict_keys(v) for k, v in o.items()} return {strutils.always_str(k): _convert_dict_keys(v) for k, v in o.items()}
@ -369,6 +375,7 @@ converters = {
10: convert_10_11, 10: convert_10_11,
11: convert_11_12, 11: convert_11_12,
12: convert_12_13, 12: convert_12_13,
13: convert_13_14,
} }

View File

@ -1,5 +1,6 @@
def map(km): def map(km):
km.add(":", "console.command ", ["commonkey", "global"], "Command prompt") km.add(":", "console.command ", ["commonkey", "global"], "Command prompt")
km.add(";", "console.command flow.comment @focus ''", ["flowlist", "flowview"], "Add comment to flow")
km.add("?", "console.view.help", ["global"], "View help") km.add("?", "console.view.help", ["global"], "View help")
km.add("B", "browser.start", ["global"], "Start an attached browser") km.add("B", "browser.start", ["global"], "Start an attached browser")
km.add("C", "console.view.commands", ["global"], "View commands") km.add("C", "console.view.commands", ["global"], "View commands")

View File

@ -33,6 +33,10 @@ def flowdetails(state, flow: mitmproxy.flow.Flow):
req = None req = None
resp = None resp = None
metadata = flow.metadata metadata = flow.metadata
comment = flow.comment
if comment:
text.append(urwid.Text([("head", "Comment: "), ("text", comment)]))
if metadata is not None and len(metadata) > 0: if metadata is not None and len(metadata) > 0:
parts = [(str(k), repr(v)) for k, v in metadata.items()] parts = [(str(k), repr(v)) for k, v in metadata.items()]

View File

@ -7,7 +7,7 @@ MITMPROXY = "mitmproxy " + VERSION
# Serialization format version. This is displayed nowhere, it just needs to be incremented by one # Serialization format version. This is displayed nowhere, it just needs to be incremented by one
# for each change in the file format. # for each change in the file format.
FLOW_FORMAT_VERSION = 13 FLOW_FORMAT_VERSION = 14
def get_dev_version() -> str: def get_dev_version() -> str:

View File

@ -0,0 +1,12 @@
from mitmproxy.test import tflow, taddons
from mitmproxy.addons.comment import Comment
def test_comment():
c = Comment()
f = tflow.tflow()
with taddons.context():
c.comment([f], "foo")
assert f.comment == "foo"

View File

@ -35,6 +35,8 @@ class TestSerialize:
sio = io.BytesIO() sio = io.BytesIO()
f = tflow.tflow() f = tflow.tflow()
f.marked = ":default:" f.marked = ":default:"
f.marked = True
f.comment = "test comment"
f.request.content = bytes(range(256)) f.request.content = bytes(range(256))
w = mitmproxy.io.FlowWriter(sio) w = mitmproxy.io.FlowWriter(sio)
w.add(f) w.add(f)

View File

@ -25,6 +25,7 @@ class TestParsing:
assert flowfilter.parse("~replay") assert flowfilter.parse("~replay")
assert flowfilter.parse("~replayq") assert flowfilter.parse("~replayq")
assert flowfilter.parse("~replays") assert flowfilter.parse("~replays")
assert flowfilter.parse("~comment .")
p = flowfilter.parse("~q ~c 10") p = flowfilter.parse("~q ~c 10")
self._dump(p) self._dump(p)
assert len(p.lst) == 2 assert len(p.lst) == 2
@ -615,6 +616,10 @@ class TestMatchingDummyFlow:
assert not self.q("~q", f) assert not self.q("~q", f)
assert not self.q("~comment .", f)
f.comment = "comment"
assert self.q("~comment .", f)
@patch('traceback.extract_tb') @patch('traceback.extract_tb')
def test_pyparsing_bug(extract_tb): def test_pyparsing_bug(extract_tb):