diff --git a/CHANGELOG.md b/CHANGELOG.md index 98b8203cf..8707e3e58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ If you depend on these features, please raise your voice in * Multiple Browsers: The `browser.start` command may be executed more than once to start additional browser sessions. (@rbdixon) * Improve readability of SHA256 fingerprint. (@wrekone) +* Metadata and Replay Flow Filters: Flows may be filtered based on metadata and replay status. (@rbdixon) * --- TODO: add new PRs above this line --- * ... and various other fixes, documentation improvements, dependency version bumps, etc. diff --git a/mitmproxy/flowfilter.py b/mitmproxy/flowfilter.py index 5b67c0766..6435a73fe 100644 --- a/mitmproxy/flowfilter.py +++ b/mitmproxy/flowfilter.py @@ -36,7 +36,6 @@ import functools import re import sys from typing import Callable, ClassVar, Optional, Sequence, Type - import pyparsing as pp from mitmproxy import flow, http, tcp @@ -382,6 +381,41 @@ class FDst(_Rex): return f.server_conn.address and self.re.search(r) +class FReplay(_Action): + code = "replay" + help = "Match replayed flows" + + def __call__(self, f): + return f.is_replay is not None + + +class FReplayClient(_Action): + code = "replayq" + help = "Match replayed client request" + + def __call__(self, f): + return f.is_replay == 'request' + + +class FReplayServer(_Action): + code = "replays" + help = "Match replayed server response" + + def __call__(self, f): + return f.is_replay == 'response' + + +class FMeta(_Rex): + code = "meta" + help = "Flow metadata" + flags = re.MULTILINE + is_binary = False + + def __call__(self, f): + m = "\n".join([f"{key}: {value}" for key, value in f.metadata.items()]) + return self.re.search(m) + + class _Int(_Action): def __init__(self, num): @@ -444,6 +478,9 @@ filter_unary: Sequence[Type[_Action]] = [ FErr, FHTTP, FMarked, + FReplay, + FReplayClient, + FReplayServer, FReq, FResp, FTCP, @@ -464,6 +501,7 @@ filter_rex: Sequence[Type[_Rex]] = [ FMethod, FSrc, FUrl, + FMeta, ] filter_int = [ FCode diff --git a/test/mitmproxy/test_flowfilter.py b/test/mitmproxy/test_flowfilter.py index 243748419..7c6a93526 100644 --- a/test/mitmproxy/test_flowfilter.py +++ b/test/mitmproxy/test_flowfilter.py @@ -24,6 +24,9 @@ class TestParsing: assert flowfilter.parse("~m foobar") assert flowfilter.parse("~u foobar") assert flowfilter.parse("~q ~c 10") + assert flowfilter.parse("~replay") + assert flowfilter.parse("~replayq") + assert flowfilter.parse("~replays") p = flowfilter.parse("~q ~c 10") self._dump(p) assert len(p.lst) == 2 @@ -296,6 +299,31 @@ class TestMatchingHTTPFlow: assert self.q("!~c 201 !~c 202", s) assert not self.q("!~c 201 !~c 200", s) + def test_replay(self): + f = tflow.tflow() + assert not self.q("~replay", f) + f.is_replay = "request" + assert self.q("~replay", f) + assert self.q("~replayq", f) + assert not self.q("~replays", f) + f.is_replay = "response" + assert self.q("~replay", f) + assert not self.q("~replayq", f) + assert self.q("~replays", f) + + def test_metadata(self): + f = tflow.tflow() + f.metadata["a"] = 1 + f.metadata["b"] = "string" + f.metadata["c"] = {"key": "value"} + assert self.q("~meta a", f) + assert not self.q("~meta no", f) + assert self.q("~meta string", f) + assert self.q("~meta key", f) + assert self.q("~meta value", f) + assert self.q("~meta \"b: string\"", f) + assert self.q("~meta \"'key': 'value'\"", f) + class TestMatchingTCPFlow: