Files
“shengyudong” 322ac74336 2025-12-25 upload
2025-12-25 11:16:59 +08:00

139 lines
3.2 KiB
Python

"""
When IO actions occur at the proxy server, they are passed down to layers as events.
Events represent the only way for layers to receive new data from sockets.
The counterpart to events are commands.
"""
import typing
import warnings
from dataclasses import dataclass
from dataclasses import is_dataclass
from typing import Any
from typing import Generic
from typing import TypeVar
from mitmproxy import flow
from mitmproxy.connection import Connection
from mitmproxy.proxy import commands
class Event:
"""
Base class for all events.
"""
def __repr__(self):
return f"{type(self).__name__}({self.__dict__!r})"
class Start(Event):
"""
Every layer initially receives a start event.
This is useful to emit events on startup.
"""
@dataclass
class ConnectionEvent(Event):
"""
All events involving connection IO.
"""
connection: Connection
@dataclass
class DataReceived(ConnectionEvent):
"""
Remote has sent some data.
"""
data: bytes
def __repr__(self):
target = type(self.connection).__name__.lower()
return f"DataReceived({target}, {self.data!r})"
class ConnectionClosed(ConnectionEvent):
"""
Remote has closed a connection.
"""
class CommandCompleted(Event):
"""
Emitted when a command has been finished, e.g.
when the master has replied or when we have established a server connection.
"""
command: commands.Command
reply: Any
def __new__(cls, *args, **kwargs):
if cls is CommandCompleted:
raise TypeError("CommandCompleted may not be instantiated directly.")
assert is_dataclass(cls)
return super().__new__(cls)
def __init_subclass__(cls, **kwargs):
command_cls = typing.get_type_hints(cls).get("command", None)
valid_command_subclass = (
isinstance(command_cls, type)
and issubclass(command_cls, commands.Command)
and command_cls is not commands.Command
)
if not valid_command_subclass:
warnings.warn(
f"{cls} needs a properly annotated command attribute.",
RuntimeWarning,
)
if command_cls in command_reply_subclasses:
other = command_reply_subclasses[command_cls]
warnings.warn(
f"Two conflicting subclasses for {command_cls}: {cls} and {other}",
RuntimeWarning,
)
command_reply_subclasses[command_cls] = cls # type: ignore
def __repr__(self):
return f"Reply({self.command!r}, {self.reply!r})"
command_reply_subclasses: dict[commands.Command, type[CommandCompleted]] = {}
@dataclass(repr=False)
class OpenConnectionCompleted(CommandCompleted):
command: commands.OpenConnection
reply: str | None
"""error message"""
@dataclass(repr=False)
class HookCompleted(CommandCompleted):
command: commands.StartHook
reply: None = None
T = TypeVar("T")
@dataclass
class MessageInjected(Event, Generic[T]):
"""
The user has injected a custom WebSocket/TCP/... message.
"""
flow: flow.Flow
message: T
@dataclass
class Wakeup(CommandCompleted):
"""
Event sent to layers that requested a wakeup using RequestWakeup.
"""
command: commands.RequestWakeup