Skip to content

Commit aad92c9

Browse files
authored
Merge pull request mitmproxy#4847 from mhils/flowfilter
Flowfilter Improvements
2 parents 0b48fdf + e364080 commit aad92c9

26 files changed

Lines changed: 778 additions & 466 deletions

examples/addons/filter-flows.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ def __init__(self):
1010
self.filter: flowfilter.TFilter = None
1111

1212
def configure(self, updated):
13-
self.filter = flowfilter.parse(ctx.options.flowfilter)
13+
if "flowfilter" in updated:
14+
self.filter = flowfilter.parse(ctx.options.flowfilter)
1415

1516
def load(self, l):
1617
l.add_option(

mitmproxy/addons/blocklist.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ def parse_spec(option: str) -> BlockSpec:
2828
except ValueError:
2929
raise ValueError(f"Invalid HTTP status code: {status}")
3030
flow_filter = flowfilter.parse(flow_patt)
31-
if not flow_filter:
32-
raise ValueError(f"Invalid filter pattern: {flow_patt}")
3331
if not RESPONSES.get(status_code):
3432
raise ValueError(f"Invalid HTTP status code: {status}")
3533

mitmproxy/addons/dumper.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,10 @@ def load(self, loader):
5959
def configure(self, updated):
6060
if "dumper_filter" in updated:
6161
if ctx.options.dumper_filter:
62-
self.filter = flowfilter.parse(ctx.options.dumper_filter)
63-
if not self.filter:
64-
raise exceptions.OptionsError(
65-
"Invalid filter expression: %s" % ctx.options.dumper_filter
66-
)
62+
try:
63+
self.filter = flowfilter.parse(ctx.options.dumper_filter)
64+
except ValueError as e:
65+
raise exceptions.OptionsError(str(e)) from e
6766
else:
6867
self.filter = None
6968

mitmproxy/addons/intercept.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ def load(self, loader):
2121
def configure(self, updated):
2222
if "intercept" in updated:
2323
if ctx.options.intercept:
24-
self.filt = flowfilter.parse(ctx.options.intercept)
25-
if not self.filt:
26-
raise exceptions.OptionsError(f"Invalid interception filter: {ctx.options.intercept}")
24+
try:
25+
self.filt = flowfilter.parse(ctx.options.intercept)
26+
except ValueError as e:
27+
raise exceptions.OptionsError(str(e)) from e
2728
ctx.options.intercept_active = True
2829
else:
2930
self.filt = None

mitmproxy/addons/readfile.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,13 @@ def load(self, loader):
3030

3131
def configure(self, updated):
3232
if "readfile_filter" in updated:
33-
filt = None
3433
if ctx.options.readfile_filter:
35-
filt = flowfilter.parse(ctx.options.readfile_filter)
36-
if not filt:
37-
raise exceptions.OptionsError(
38-
"Invalid readfile filter: %s" % ctx.options.readfile_filter
39-
)
40-
self.filter = filt
34+
try:
35+
self.filter = flowfilter.parse(ctx.options.readfile_filter)
36+
except ValueError as e:
37+
raise exceptions.OptionsError(str(e)) from e
38+
else:
39+
self.filter = None
4140

4241
async def load_flows(self, fo: typing.IO[bytes]) -> int:
4342
cnt = 0

mitmproxy/addons/save.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,10 @@ def configure(self, updated):
4848
# We're already streaming - stop the previous stream and restart
4949
if "save_stream_filter" in updated:
5050
if ctx.options.save_stream_filter:
51-
self.filt = flowfilter.parse(ctx.options.save_stream_filter)
52-
if not self.filt:
53-
raise exceptions.OptionsError(
54-
"Invalid filter specification: %s" % ctx.options.save_stream_filter
55-
)
51+
try:
52+
self.filt = flowfilter.parse(ctx.options.save_stream_filter)
53+
except ValueError as e:
54+
raise exceptions.OptionsError(str(e)) from e
5655
else:
5756
self.filt = None
5857
if "save_stream_file" in updated or "save_stream_filter" in updated:

mitmproxy/addons/stickyauth.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@ def load(self, loader):
1919
def configure(self, updated):
2020
if "stickyauth" in updated:
2121
if ctx.options.stickyauth:
22-
flt = flowfilter.parse(ctx.options.stickyauth)
23-
if not flt:
24-
raise exceptions.OptionsError(
25-
"stickyauth: invalid filter expression: %s" % ctx.options.stickyauth
26-
)
27-
self.flt = flt
22+
try:
23+
self.flt = flowfilter.parse(ctx.options.stickyauth)
24+
except ValueError as e:
25+
raise exceptions.OptionsError(str(e)) from e
2826
else:
2927
self.flt = None
3028

mitmproxy/addons/stickycookie.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,10 @@ def load(self, loader):
4343
def configure(self, updated):
4444
if "stickycookie" in updated:
4545
if ctx.options.stickycookie:
46-
flt = flowfilter.parse(ctx.options.stickycookie)
47-
if not flt:
48-
raise exceptions.OptionsError(
49-
"stickycookie: invalid filter expression: %s" % ctx.options.stickycookie
50-
)
51-
self.flt = flt
46+
try:
47+
self.flt = flowfilter.parse(ctx.options.stickycookie)
48+
except ValueError as e:
49+
raise exceptions.OptionsError(str(e)) from e
5250
else:
5351
self.flt = None
5452

mitmproxy/addons/view.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,6 @@ def generate(self, f: mitmproxy.flow.Flow) -> int:
115115
raise NotImplementedError()
116116

117117

118-
matchall = flowfilter.parse("~http | ~tcp")
119-
120118
orders = [
121119
("t", "time"),
122120
("m", "method"),
@@ -129,7 +127,7 @@ class View(collections.abc.Sequence):
129127
def __init__(self):
130128
super().__init__()
131129
self._store = collections.OrderedDict()
132-
self.filter = matchall
130+
self.filter = flowfilter.match_all
133131
# Should we show only marked flows?
134132
self.show_marked = False
135133

@@ -326,15 +324,14 @@ def set_filter_cmd(self, filter_expr: str) -> None:
326324
"""
327325
filt = None
328326
if filter_expr:
329-
filt = flowfilter.parse(filter_expr)
330-
if not filt:
331-
raise exceptions.CommandError(
332-
"Invalid interception filter: %s" % filter_expr
333-
)
327+
try:
328+
filt = flowfilter.parse(filter_expr)
329+
except ValueError as e:
330+
raise exceptions.CommandError(str(e)) from e
334331
self.set_filter(filt)
335332

336333
def set_filter(self, flt: typing.Optional[flowfilter.TFilter]):
337-
self.filter = flt or matchall
334+
self.filter = flt or flowfilter.match_all
338335
self._refilter()
339336

340337
# View Updates
@@ -454,9 +451,10 @@ def resolve(self, flow_spec: str) -> typing.Sequence[mitmproxy.flow.Flow]:
454451
ids = flow_spec[1:].split(",")
455452
return [i for i in self._store.values() if i.id in ids]
456453
else:
457-
filt = flowfilter.parse(flow_spec)
458-
if not filt:
459-
raise exceptions.CommandError("Invalid flow filter: %s" % flow_spec)
454+
try:
455+
filt = flowfilter.parse(flow_spec)
456+
except ValueError as e:
457+
raise exceptions.CommandError(str(e)) from e
460458
return [i for i in self._store.values() if filt(i)]
461459

462460
@command.command("view.flows.create")
@@ -547,11 +545,10 @@ def configure(self, updated):
547545
if "view_filter" in updated:
548546
filt = None
549547
if ctx.options.view_filter:
550-
filt = flowfilter.parse(ctx.options.view_filter)
551-
if not filt:
552-
raise exceptions.OptionsError(
553-
"Invalid interception filter: %s" % ctx.options.view_filter
554-
)
548+
try:
549+
filt = flowfilter.parse(ctx.options.view_filter)
550+
except ValueError as e:
551+
raise exceptions.OptionsError(str(e)) from e
555552
self.set_filter(filt)
556553
if "view_order" in updated:
557554
if ctx.options.view_order not in self.orders:

mitmproxy/flowfilter.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import functools
3636
import re
3737
import sys
38-
from typing import Callable, ClassVar, Optional, Sequence, Type
38+
from typing import ClassVar, Sequence, Type, Protocol, Union
3939
import pyparsing as pp
4040

4141
from mitmproxy import flow, http, tcp
@@ -135,6 +135,14 @@ def __call__(self, f):
135135
return bool(f.response)
136136

137137

138+
class FAll(_Action):
139+
code = "all"
140+
help = "Match all flows"
141+
142+
def __call__(self, f: flow.Flow):
143+
return True
144+
145+
138146
class _Rex(_Action):
139147
flags = 0
140148
is_binary = True
@@ -504,6 +512,7 @@ def __call__(self, f):
504512
FResp,
505513
FTCP,
506514
FWebSocket,
515+
FAll,
507516
]
508517
filter_rex: Sequence[Type[_Rex]] = [
509518
FBod,
@@ -583,21 +592,31 @@ def _make():
583592

584593

585594
bnf = _make()
586-
TFilter = Callable[[flow.Flow], bool]
587595

588596

589-
def parse(s: str) -> Optional[TFilter]:
597+
class TFilter(Protocol):
598+
pattern: str
599+
600+
def __call__(self, f: flow.Flow) -> bool:
601+
... # pragma: no cover
602+
603+
604+
def parse(s: str) -> TFilter:
605+
"""
606+
Parse a filter expression and return the compiled filter function.
607+
If the filter syntax is invalid, `ValueError` is raised.
608+
"""
609+
if not s:
610+
raise ValueError("Empty filter expression")
590611
try:
591612
flt = bnf.parseString(s, parseAll=True)[0]
592613
flt.pattern = s
593614
return flt
594-
except pp.ParseException:
595-
return None
596-
except ValueError:
597-
return None
615+
except (pp.ParseException, ValueError) as e:
616+
raise ValueError(f"Invalid filter expression: {s!r}") from e
598617

599618

600-
def match(flt, flow):
619+
def match(flt: Union[str, TFilter], flow: flow.Flow) -> bool:
601620
"""
602621
Matches a flow against a compiled filter expression.
603622
Returns True if matched, False if not.
@@ -607,13 +626,15 @@ def match(flt, flow):
607626
"""
608627
if isinstance(flt, str):
609628
flt = parse(flt)
610-
if not flt:
611-
raise ValueError("Invalid filter expression.")
612629
if flt:
613630
return flt(flow)
614631
return True
615632

616633

634+
match_all: TFilter = parse("~all")
635+
"""A filter function that matches all flows"""
636+
637+
617638
help = []
618639
for a in filter_unary:
619640
help.append(

0 commit comments

Comments
 (0)