mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 02:10:59 +00:00
improve flowfilter api: raise on invalid input, add ~all
This commit is contained in:
parent
adfccb90a5
commit
c43a2ef8dc
@ -10,7 +10,8 @@ class Filter:
|
|||||||
self.filter: flowfilter.TFilter = None
|
self.filter: flowfilter.TFilter = None
|
||||||
|
|
||||||
def configure(self, updated):
|
def configure(self, updated):
|
||||||
self.filter = flowfilter.parse(ctx.options.flowfilter)
|
if "flowfilter" in updated:
|
||||||
|
self.filter = flowfilter.parse(ctx.options.flowfilter)
|
||||||
|
|
||||||
def load(self, l):
|
def load(self, l):
|
||||||
l.add_option(
|
l.add_option(
|
||||||
|
@ -28,8 +28,6 @@ def parse_spec(option: str) -> BlockSpec:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError(f"Invalid HTTP status code: {status}")
|
raise ValueError(f"Invalid HTTP status code: {status}")
|
||||||
flow_filter = flowfilter.parse(flow_patt)
|
flow_filter = flowfilter.parse(flow_patt)
|
||||||
if not flow_filter:
|
|
||||||
raise ValueError(f"Invalid filter pattern: {flow_patt}")
|
|
||||||
if not RESPONSES.get(status_code):
|
if not RESPONSES.get(status_code):
|
||||||
raise ValueError(f"Invalid HTTP status code: {status}")
|
raise ValueError(f"Invalid HTTP status code: {status}")
|
||||||
|
|
||||||
|
@ -59,11 +59,10 @@ class Dumper:
|
|||||||
def configure(self, updated):
|
def configure(self, updated):
|
||||||
if "dumper_filter" in updated:
|
if "dumper_filter" in updated:
|
||||||
if ctx.options.dumper_filter:
|
if ctx.options.dumper_filter:
|
||||||
self.filter = flowfilter.parse(ctx.options.dumper_filter)
|
try:
|
||||||
if not self.filter:
|
self.filter = flowfilter.parse(ctx.options.dumper_filter)
|
||||||
raise exceptions.OptionsError(
|
except ValueError as e:
|
||||||
"Invalid filter expression: %s" % ctx.options.dumper_filter
|
raise exceptions.OptionsError(str(e)) from e
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.filter = None
|
self.filter = None
|
||||||
|
|
||||||
|
@ -21,9 +21,10 @@ class Intercept:
|
|||||||
def configure(self, updated):
|
def configure(self, updated):
|
||||||
if "intercept" in updated:
|
if "intercept" in updated:
|
||||||
if ctx.options.intercept:
|
if ctx.options.intercept:
|
||||||
self.filt = flowfilter.parse(ctx.options.intercept)
|
try:
|
||||||
if not self.filt:
|
self.filt = flowfilter.parse(ctx.options.intercept)
|
||||||
raise exceptions.OptionsError(f"Invalid interception filter: {ctx.options.intercept}")
|
except ValueError as e:
|
||||||
|
raise exceptions.OptionsError(str(e)) from e
|
||||||
ctx.options.intercept_active = True
|
ctx.options.intercept_active = True
|
||||||
else:
|
else:
|
||||||
self.filt = None
|
self.filt = None
|
||||||
|
@ -30,14 +30,13 @@ class ReadFile:
|
|||||||
|
|
||||||
def configure(self, updated):
|
def configure(self, updated):
|
||||||
if "readfile_filter" in updated:
|
if "readfile_filter" in updated:
|
||||||
filt = None
|
|
||||||
if ctx.options.readfile_filter:
|
if ctx.options.readfile_filter:
|
||||||
filt = flowfilter.parse(ctx.options.readfile_filter)
|
try:
|
||||||
if not filt:
|
self.filter = flowfilter.parse(ctx.options.readfile_filter)
|
||||||
raise exceptions.OptionsError(
|
except ValueError as e:
|
||||||
"Invalid readfile filter: %s" % ctx.options.readfile_filter
|
raise exceptions.OptionsError(str(e)) from e
|
||||||
)
|
else:
|
||||||
self.filter = filt
|
self.filter = None
|
||||||
|
|
||||||
async def load_flows(self, fo: typing.IO[bytes]) -> int:
|
async def load_flows(self, fo: typing.IO[bytes]) -> int:
|
||||||
cnt = 0
|
cnt = 0
|
||||||
|
@ -48,11 +48,10 @@ class Save:
|
|||||||
# We're already streaming - stop the previous stream and restart
|
# We're already streaming - stop the previous stream and restart
|
||||||
if "save_stream_filter" in updated:
|
if "save_stream_filter" in updated:
|
||||||
if ctx.options.save_stream_filter:
|
if ctx.options.save_stream_filter:
|
||||||
self.filt = flowfilter.parse(ctx.options.save_stream_filter)
|
try:
|
||||||
if not self.filt:
|
self.filt = flowfilter.parse(ctx.options.save_stream_filter)
|
||||||
raise exceptions.OptionsError(
|
except ValueError as e:
|
||||||
"Invalid filter specification: %s" % ctx.options.save_stream_filter
|
raise exceptions.OptionsError(str(e)) from e
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.filt = None
|
self.filt = None
|
||||||
if "save_stream_file" in updated or "save_stream_filter" in updated:
|
if "save_stream_file" in updated or "save_stream_filter" in updated:
|
||||||
|
@ -19,12 +19,10 @@ class StickyAuth:
|
|||||||
def configure(self, updated):
|
def configure(self, updated):
|
||||||
if "stickyauth" in updated:
|
if "stickyauth" in updated:
|
||||||
if ctx.options.stickyauth:
|
if ctx.options.stickyauth:
|
||||||
flt = flowfilter.parse(ctx.options.stickyauth)
|
try:
|
||||||
if not flt:
|
self.flt = flowfilter.parse(ctx.options.stickyauth)
|
||||||
raise exceptions.OptionsError(
|
except ValueError as e:
|
||||||
"stickyauth: invalid filter expression: %s" % ctx.options.stickyauth
|
raise exceptions.OptionsError(str(e)) from e
|
||||||
)
|
|
||||||
self.flt = flt
|
|
||||||
else:
|
else:
|
||||||
self.flt = None
|
self.flt = None
|
||||||
|
|
||||||
|
@ -43,12 +43,10 @@ class StickyCookie:
|
|||||||
def configure(self, updated):
|
def configure(self, updated):
|
||||||
if "stickycookie" in updated:
|
if "stickycookie" in updated:
|
||||||
if ctx.options.stickycookie:
|
if ctx.options.stickycookie:
|
||||||
flt = flowfilter.parse(ctx.options.stickycookie)
|
try:
|
||||||
if not flt:
|
self.flt = flowfilter.parse(ctx.options.stickycookie)
|
||||||
raise exceptions.OptionsError(
|
except ValueError as e:
|
||||||
"stickycookie: invalid filter expression: %s" % ctx.options.stickycookie
|
raise exceptions.OptionsError(str(e)) from e
|
||||||
)
|
|
||||||
self.flt = flt
|
|
||||||
else:
|
else:
|
||||||
self.flt = None
|
self.flt = None
|
||||||
|
|
||||||
|
@ -114,9 +114,6 @@ class OrderKeySize(_OrderKey):
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
matchall = flowfilter.parse("~http | ~tcp")
|
|
||||||
|
|
||||||
orders = [
|
orders = [
|
||||||
("t", "time"),
|
("t", "time"),
|
||||||
("m", "method"),
|
("m", "method"),
|
||||||
@ -129,7 +126,7 @@ class View(collections.abc.Sequence):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._store = collections.OrderedDict()
|
self._store = collections.OrderedDict()
|
||||||
self.filter = matchall
|
self.filter = flowfilter.match_all
|
||||||
# Should we show only marked flows?
|
# Should we show only marked flows?
|
||||||
self.show_marked = False
|
self.show_marked = False
|
||||||
|
|
||||||
@ -326,11 +323,10 @@ class View(collections.abc.Sequence):
|
|||||||
"""
|
"""
|
||||||
filt = None
|
filt = None
|
||||||
if filter_expr:
|
if filter_expr:
|
||||||
filt = flowfilter.parse(filter_expr)
|
try:
|
||||||
if not filt:
|
filt = flowfilter.parse(filter_expr)
|
||||||
raise exceptions.CommandError(
|
except ValueError as e:
|
||||||
"Invalid interception filter: %s" % filter_expr
|
raise exceptions.CommandError(str(e)) from e
|
||||||
)
|
|
||||||
self.set_filter(filt)
|
self.set_filter(filt)
|
||||||
|
|
||||||
def set_filter(self, flt: typing.Optional[flowfilter.TFilter]):
|
def set_filter(self, flt: typing.Optional[flowfilter.TFilter]):
|
||||||
@ -454,9 +450,10 @@ class View(collections.abc.Sequence):
|
|||||||
ids = flow_spec[1:].split(",")
|
ids = flow_spec[1:].split(",")
|
||||||
return [i for i in self._store.values() if i.id in ids]
|
return [i for i in self._store.values() if i.id in ids]
|
||||||
else:
|
else:
|
||||||
filt = flowfilter.parse(flow_spec)
|
try:
|
||||||
if not filt:
|
filt = flowfilter.parse(flow_spec)
|
||||||
raise exceptions.CommandError("Invalid flow filter: %s" % flow_spec)
|
except ValueError as e:
|
||||||
|
raise exceptions.CommandError(str(e)) from e
|
||||||
return [i for i in self._store.values() if filt(i)]
|
return [i for i in self._store.values() if filt(i)]
|
||||||
|
|
||||||
@command.command("view.flows.create")
|
@command.command("view.flows.create")
|
||||||
@ -547,11 +544,10 @@ class View(collections.abc.Sequence):
|
|||||||
if "view_filter" in updated:
|
if "view_filter" in updated:
|
||||||
filt = None
|
filt = None
|
||||||
if ctx.options.view_filter:
|
if ctx.options.view_filter:
|
||||||
filt = flowfilter.parse(ctx.options.view_filter)
|
try:
|
||||||
if not filt:
|
filt = flowfilter.parse(ctx.options.view_filter)
|
||||||
raise exceptions.OptionsError(
|
except ValueError as e:
|
||||||
"Invalid interception filter: %s" % ctx.options.view_filter
|
raise exceptions.OptionsError(str(e)) from e
|
||||||
)
|
|
||||||
self.set_filter(filt)
|
self.set_filter(filt)
|
||||||
if "view_order" in updated:
|
if "view_order" in updated:
|
||||||
if ctx.options.view_order not in self.orders:
|
if ctx.options.view_order not in self.orders:
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
import functools
|
import functools
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import Callable, ClassVar, Optional, Sequence, Type
|
from typing import Callable, ClassVar, Optional, Sequence, Type, Protocol, Union
|
||||||
import pyparsing as pp
|
import pyparsing as pp
|
||||||
|
|
||||||
from mitmproxy import flow, http, tcp
|
from mitmproxy import flow, http, tcp
|
||||||
@ -135,6 +135,14 @@ class FResp(_Action):
|
|||||||
return bool(f.response)
|
return bool(f.response)
|
||||||
|
|
||||||
|
|
||||||
|
class FAll(_Action):
|
||||||
|
code = "all"
|
||||||
|
help = "Match all flows"
|
||||||
|
|
||||||
|
def __call__(self, f: flow.Flow):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class _Rex(_Action):
|
class _Rex(_Action):
|
||||||
flags = 0
|
flags = 0
|
||||||
is_binary = True
|
is_binary = True
|
||||||
@ -504,6 +512,7 @@ filter_unary: Sequence[Type[_Action]] = [
|
|||||||
FResp,
|
FResp,
|
||||||
FTCP,
|
FTCP,
|
||||||
FWebSocket,
|
FWebSocket,
|
||||||
|
FAll,
|
||||||
]
|
]
|
||||||
filter_rex: Sequence[Type[_Rex]] = [
|
filter_rex: Sequence[Type[_Rex]] = [
|
||||||
FBod,
|
FBod,
|
||||||
@ -583,21 +592,31 @@ def _make():
|
|||||||
|
|
||||||
|
|
||||||
bnf = _make()
|
bnf = _make()
|
||||||
TFilter = Callable[[flow.Flow], bool]
|
|
||||||
|
|
||||||
|
|
||||||
def parse(s: str) -> Optional[TFilter]:
|
class TFilter(Protocol):
|
||||||
|
pattern: str
|
||||||
|
|
||||||
|
def __call__(self, f: flow.Flow) -> bool:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def parse(s: str) -> TFilter:
|
||||||
|
"""
|
||||||
|
Parse a filter expression and return the compiled filter function.
|
||||||
|
If the filter syntax is invalid, `ValueError` is raised.
|
||||||
|
"""
|
||||||
|
if not s:
|
||||||
|
raise ValueError("Empty filter expression")
|
||||||
try:
|
try:
|
||||||
flt = bnf.parseString(s, parseAll=True)[0]
|
flt = bnf.parseString(s, parseAll=True)[0]
|
||||||
flt.pattern = s
|
flt.pattern = s
|
||||||
return flt
|
return flt
|
||||||
except pp.ParseException:
|
except (pp.ParseException, ValueError) as e:
|
||||||
return None
|
raise ValueError(f"Invalid filter expression: {s!r}") from e
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def match(flt, flow):
|
def match(flt: Union[str, TFilter], flow: flow.Flow) -> bool:
|
||||||
"""
|
"""
|
||||||
Matches a flow against a compiled filter expression.
|
Matches a flow against a compiled filter expression.
|
||||||
Returns True if matched, False if not.
|
Returns True if matched, False if not.
|
||||||
@ -607,13 +626,15 @@ def match(flt, flow):
|
|||||||
"""
|
"""
|
||||||
if isinstance(flt, str):
|
if isinstance(flt, str):
|
||||||
flt = parse(flt)
|
flt = parse(flt)
|
||||||
if not flt:
|
|
||||||
raise ValueError("Invalid filter expression.")
|
|
||||||
if flt:
|
if flt:
|
||||||
return flt(flow)
|
return flt(flow)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
match_all: TFilter = parse("~all")
|
||||||
|
"""A filter function that matches all flows"""
|
||||||
|
|
||||||
|
|
||||||
help = []
|
help = []
|
||||||
for a in filter_unary:
|
for a in filter_unary:
|
||||||
help.append(
|
help.append(
|
||||||
|
@ -2,10 +2,6 @@ import typing
|
|||||||
from mitmproxy import flowfilter
|
from mitmproxy import flowfilter
|
||||||
|
|
||||||
|
|
||||||
def _match_all(flow) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def parse_spec(option: str) -> typing.Tuple[flowfilter.TFilter, str, str]:
|
def parse_spec(option: str) -> typing.Tuple[flowfilter.TFilter, str, str]:
|
||||||
"""
|
"""
|
||||||
Parse strings in the following format:
|
Parse strings in the following format:
|
||||||
@ -17,7 +13,7 @@ def parse_spec(option: str) -> typing.Tuple[flowfilter.TFilter, str, str]:
|
|||||||
parts = rem.split(sep, 2)
|
parts = rem.split(sep, 2)
|
||||||
if len(parts) == 2:
|
if len(parts) == 2:
|
||||||
subject, replacement = parts
|
subject, replacement = parts
|
||||||
return _match_all, subject, replacement
|
return flowfilter.match_all, subject, replacement
|
||||||
elif len(parts) == 3:
|
elif len(parts) == 3:
|
||||||
patt, subject, replacement = parts
|
patt, subject, replacement = parts
|
||||||
flow_filter = flowfilter.parse(patt)
|
flow_filter = flowfilter.parse(patt)
|
||||||
|
@ -43,7 +43,7 @@ class TestReadFile:
|
|||||||
rf = readfile.ReadFile()
|
rf = readfile.ReadFile()
|
||||||
with taddons.context(rf) as tctx:
|
with taddons.context(rf) as tctx:
|
||||||
tctx.configure(rf, readfile_filter="~q")
|
tctx.configure(rf, readfile_filter="~q")
|
||||||
with pytest.raises(Exception, match="Invalid readfile filter"):
|
with pytest.raises(Exception, match="Invalid filter expression"):
|
||||||
tctx.configure(rf, readfile_filter="~~")
|
tctx.configure(rf, readfile_filter="~~")
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -16,7 +16,7 @@ class TestStickyCookie:
|
|||||||
def test_config(self):
|
def test_config(self):
|
||||||
sc = stickycookie.StickyCookie()
|
sc = stickycookie.StickyCookie()
|
||||||
with taddons.context(sc) as tctx:
|
with taddons.context(sc) as tctx:
|
||||||
with pytest.raises(Exception, match="invalid filter"):
|
with pytest.raises(Exception, match="Invalid filter expression"):
|
||||||
tctx.configure(sc, stickycookie="~b")
|
tctx.configure(sc, stickycookie="~b")
|
||||||
|
|
||||||
tctx.configure(sc, stickycookie="foo")
|
tctx.configure(sc, stickycookie="foo")
|
||||||
|
@ -268,7 +268,7 @@ def test_resolve():
|
|||||||
assert m(tctx.command(v.resolve, "@unmarked")) == ["PUT", "GET", "PUT"]
|
assert m(tctx.command(v.resolve, "@unmarked")) == ["PUT", "GET", "PUT"]
|
||||||
assert m(tctx.command(v.resolve, "@all")) == ["GET", "PUT", "GET", "PUT"]
|
assert m(tctx.command(v.resolve, "@all")) == ["GET", "PUT", "GET", "PUT"]
|
||||||
|
|
||||||
with pytest.raises(exceptions.CommandError, match="Invalid flow filter"):
|
with pytest.raises(exceptions.CommandError, match="Invalid filter expression"):
|
||||||
tctx.command(v.resolve, "~")
|
tctx.command(v.resolve, "~")
|
||||||
|
|
||||||
|
|
||||||
@ -608,7 +608,7 @@ def test_configure():
|
|||||||
v = view.View()
|
v = view.View()
|
||||||
with taddons.context(v) as tctx:
|
with taddons.context(v) as tctx:
|
||||||
tctx.configure(v, view_filter="~q")
|
tctx.configure(v, view_filter="~q")
|
||||||
with pytest.raises(Exception, match="Invalid interception filter"):
|
with pytest.raises(Exception, match="Invalid filter expression"):
|
||||||
tctx.configure(v, view_filter="~~")
|
tctx.configure(v, view_filter="~~")
|
||||||
|
|
||||||
tctx.configure(v, view_order="method")
|
tctx.configure(v, view_order="method")
|
||||||
|
@ -13,10 +13,12 @@ class TestParsing:
|
|||||||
assert c.getvalue()
|
assert c.getvalue()
|
||||||
|
|
||||||
def test_parse_err(self):
|
def test_parse_err(self):
|
||||||
assert flowfilter.parse("~h [") is None
|
with pytest.raises(ValueError):
|
||||||
|
flowfilter.parse("~b")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
flowfilter.parse("~h [")
|
||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
assert not flowfilter.parse("~b")
|
|
||||||
assert flowfilter.parse("~q")
|
assert flowfilter.parse("~q")
|
||||||
assert flowfilter.parse("~c 10")
|
assert flowfilter.parse("~c 10")
|
||||||
assert flowfilter.parse("~m foobar")
|
assert flowfilter.parse("~m foobar")
|
||||||
|
@ -16,5 +16,5 @@ def test_parse_spec():
|
|||||||
with pytest.raises(ValueError, match="Invalid number of parameters"):
|
with pytest.raises(ValueError, match="Invalid number of parameters"):
|
||||||
parse_spec("/")
|
parse_spec("/")
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Invalid filter pattern"):
|
with pytest.raises(ValueError, match="Invalid filter expression"):
|
||||||
parse_spec("/~b/one/two")
|
parse_spec("/~b/one/two")
|
||||||
|
@ -14,7 +14,6 @@ export interface OptionsState {
|
|||||||
ciphers_server: string | undefined
|
ciphers_server: string | undefined
|
||||||
client_certs: string | undefined
|
client_certs: string | undefined
|
||||||
client_replay: string[]
|
client_replay: string[]
|
||||||
client_replay_concurrency: number
|
|
||||||
command_history: boolean
|
command_history: boolean
|
||||||
confdir: string
|
confdir: string
|
||||||
connection_strategy: string
|
connection_strategy: string
|
||||||
@ -100,7 +99,6 @@ export const defaultState: OptionsState = {
|
|||||||
ciphers_server: undefined,
|
ciphers_server: undefined,
|
||||||
client_certs: undefined,
|
client_certs: undefined,
|
||||||
client_replay: [],
|
client_replay: [],
|
||||||
client_replay_concurrency: 1,
|
|
||||||
command_history: true,
|
command_history: true,
|
||||||
confdir: "~/.mitmproxy",
|
confdir: "~/.mitmproxy",
|
||||||
connection_strategy: "eager",
|
connection_strategy: "eager",
|
||||||
|
Loading…
Reference in New Issue
Block a user