Merge pull request #5643 from mhils/h3-cv
Improve HTTP/3 Frame Contentview
This commit is contained in:
commit
dbf424169d
|
@ -159,23 +159,6 @@ def get_message_content_view(
|
|||
return description, lines, error
|
||||
|
||||
|
||||
def get_proto_content_view(
|
||||
viewname: str,
|
||||
data: bytes,
|
||||
flow: Union[TCPFlow, UDPFlow],
|
||||
):
|
||||
viewmode = get(viewname)
|
||||
if not viewmode:
|
||||
viewmode = get("auto")
|
||||
|
||||
# https://github.com/mitmproxy/mitmproxy/pull/3970#issuecomment-623024447
|
||||
assert viewmode
|
||||
|
||||
description, lines, error = get_content_view(viewmode, data, flow=flow)
|
||||
|
||||
return description, lines, error
|
||||
|
||||
|
||||
def get_content_view(
|
||||
viewmode: View,
|
||||
data: bytes,
|
||||
|
|
|
@ -55,10 +55,27 @@ class Frame:
|
|||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StreamType:
|
||||
"""Representation of an HTTP/3 stream types."""
|
||||
type: int
|
||||
|
||||
def pretty(self):
|
||||
stream_type = {
|
||||
0x00: "Control Stream",
|
||||
0x01: "Push Stream",
|
||||
0x02: "QPACK Encoder Stream",
|
||||
0x03: "QPACK Decoder Stream",
|
||||
}.get(self.type, f"0x{self.type:x} Stream")
|
||||
return [
|
||||
[("header", stream_type)]
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConnectionState:
|
||||
message_count: int = 0
|
||||
frames: dict[int, list[Frame]] = field(default_factory=dict)
|
||||
frames: dict[int, list[Frame | StreamType]] = field(default_factory=dict)
|
||||
client_buf: bytearray = field(default_factory=bytearray)
|
||||
server_buf: bytearray = field(default_factory=bytearray)
|
||||
|
||||
|
@ -88,9 +105,15 @@ class ViewHttp3(base.View):
|
|||
buf = state.server_buf
|
||||
buf += message.content
|
||||
|
||||
# TODO: It would be much better to know if the stream is unidirectional.
|
||||
if buf.startswith(b"\x00\x04"):
|
||||
del buf[:1]
|
||||
if state.message_count == 0 and flow.metadata["quic_is_unidirectional"]:
|
||||
h3_buf = Buffer(data=bytes(buf[:8]))
|
||||
stream_type = h3_buf.pull_uint_var()
|
||||
consumed = h3_buf.tell()
|
||||
assert consumed == 1
|
||||
del buf[:consumed]
|
||||
state.frames[0] = [
|
||||
StreamType(stream_type)
|
||||
]
|
||||
|
||||
while True:
|
||||
h3_buf = Buffer(data=bytes(buf[:16]))
|
||||
|
@ -130,7 +153,7 @@ class ViewHttp3(base.View):
|
|||
return 2 * float(bool(flow and is_h3_alpn(flow.client_conn.alpn))) * float(isinstance(flow, tcp.TCPFlow))
|
||||
|
||||
|
||||
def fmt_frames(frames: list[Frame]) -> Iterator[base.TViewLine]:
|
||||
def fmt_frames(frames: list[Frame | StreamType]) -> Iterator[base.TViewLine]:
|
||||
for i, frame in enumerate(frames):
|
||||
if i > 0:
|
||||
yield [("text", "")]
|
||||
|
|
|
@ -255,22 +255,11 @@ class FlowDetails(tabs.Tabs):
|
|||
|
||||
viewmode = self.master.commands.call("console.flowview.mode")
|
||||
|
||||
# Merge adjacent TCP "messages". For detailed explanation of this code block see:
|
||||
# https://github.com/mitmproxy/mitmproxy/pull/3970/files/469bd32582f764f9a29607efa4f5b04bd87961fb#r418670880
|
||||
from_client = None
|
||||
messages = []
|
||||
for message in flow.messages:
|
||||
if message.from_client is not from_client:
|
||||
messages.append(message.content)
|
||||
from_client = message.from_client
|
||||
else:
|
||||
messages[-1] += message.content
|
||||
|
||||
widget_lines = []
|
||||
|
||||
from_client = flow.messages[0].from_client
|
||||
for m in messages:
|
||||
_, lines, _ = contentviews.get_proto_content_view(viewmode, m, flow)
|
||||
for m in flow.messages:
|
||||
_, lines, _ = contentviews.get_message_content_view(viewmode, m, flow)
|
||||
|
||||
for line in lines:
|
||||
if from_client:
|
||||
|
|
|
@ -15,14 +15,6 @@ if http3 is None:
|
|||
b"\x01\x1d\x00\x00\xd1\xc1\xd7P\x8a\x08\x9d\\\x0b\x81p\xdcx\x0f\x03_P\x88%\xb6P\xc3\xab\xbc\xda\xe0\xdd",
|
||||
# broken HEADERS
|
||||
b"\x01\x1d\x00\x00\xd1\xc1\xd7P\x8a\x08\x9d\\\x0b\x81p\xdcx\x0f\x03_P\x88%\xb6P\xc3\xab\xff\xff\xff\xff",
|
||||
# SETTINGS
|
||||
b"\x00\x04\r\x06\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x07\x00",
|
||||
# unknown setting
|
||||
b"\x00\x04\r\x3f\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x07\x00",
|
||||
# out of bounds
|
||||
b"\x00\x04\r\x06\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x42\x00",
|
||||
# incomplete
|
||||
b"\x00\x04\r\x06\xff\xff\xff",
|
||||
# headers + data
|
||||
(
|
||||
b'\x01@I\x00\x00\xdb_\'\x93I|\xa5\x89\xd3M\x1fj\x12q\xd8\x82\xa6\x0bP\xb0\xd0C\x1b_M\x90\xd0bXt\x1eT\xad\x8f~\xfdp'
|
||||
|
@ -38,6 +30,28 @@ def test_view_http3(data):
|
|||
t = tflow.ttcpflow(messages=[
|
||||
TCPMessage(from_client=len(data) > 16, content=data)
|
||||
])
|
||||
t.metadata["quic_is_unidirectional"] = False
|
||||
assert (v(b"", flow=t, tcp_message=t.messages[0]))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
# SETTINGS
|
||||
b"\x00\x04\r\x06\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x07\x00",
|
||||
# unknown setting
|
||||
b"\x00\x04\r\x3f\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x07\x00",
|
||||
# out of bounds
|
||||
b"\x00\x04\r\x06\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x42\x00",
|
||||
# incomplete
|
||||
b"\x00\x04\r\x06\xff\xff\xff",
|
||||
# QPACK encoder stream
|
||||
b"\x02",
|
||||
])
|
||||
def test_view_http3_unidirectional(data):
|
||||
v = full_eval(http3.ViewHttp3())
|
||||
t = tflow.ttcpflow(messages=[
|
||||
TCPMessage(from_client=len(data) > 16, content=data)
|
||||
])
|
||||
t.metadata["quic_is_unidirectional"] = True
|
||||
assert (v(b"", flow=t, tcp_message=t.messages[0]))
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue