117 lines
3.3 KiB
Python
117 lines
3.3 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import logging
|
||
|
|
import typing
|
||
|
|
from abc import abstractmethod
|
||
|
|
from dataclasses import dataclass
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Literal
|
||
|
|
|
||
|
|
from mitmproxy import http
|
||
|
|
from mitmproxy import tcp
|
||
|
|
from mitmproxy import udp
|
||
|
|
from mitmproxy.dns import DNSMessage
|
||
|
|
from mitmproxy.flow import Flow
|
||
|
|
from mitmproxy.websocket import WebSocketMessage
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
type SyntaxHighlight = Literal["css", "javascript", "xml", "yaml", "none", "error"]
|
||
|
|
|
||
|
|
|
||
|
|
@typing.runtime_checkable
|
||
|
|
class Contentview(typing.Protocol):
|
||
|
|
"""
|
||
|
|
Base class for all contentviews.
|
||
|
|
"""
|
||
|
|
|
||
|
|
@property
|
||
|
|
def name(self) -> str:
|
||
|
|
"""
|
||
|
|
The name of this contentview, e.g. "XML/HTML".
|
||
|
|
Inferred from the class name by default.
|
||
|
|
"""
|
||
|
|
return type(self).__name__.removesuffix("Contentview")
|
||
|
|
|
||
|
|
@property
|
||
|
|
def syntax_highlight(self) -> SyntaxHighlight:
|
||
|
|
"""Optional syntax highlighting that should be applied to the prettified output."""
|
||
|
|
return "none"
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def prettify(
|
||
|
|
self,
|
||
|
|
data: bytes,
|
||
|
|
metadata: Metadata,
|
||
|
|
) -> str:
|
||
|
|
"""
|
||
|
|
Transform raw data into human-readable output.
|
||
|
|
May raise an exception (e.g. `ValueError`) if data cannot be prettified.
|
||
|
|
"""
|
||
|
|
|
||
|
|
def render_priority(
|
||
|
|
self,
|
||
|
|
data: bytes,
|
||
|
|
metadata: Metadata,
|
||
|
|
) -> float:
|
||
|
|
"""
|
||
|
|
Return the priority of this view for rendering `data`.
|
||
|
|
If no particular view is chosen by the user, the view with the highest priority is selected.
|
||
|
|
If this view does not support the given data, return a float < 0.
|
||
|
|
"""
|
||
|
|
return 0
|
||
|
|
|
||
|
|
def __lt__(self, other):
|
||
|
|
return self.name.__lt__(other.name)
|
||
|
|
|
||
|
|
|
||
|
|
@typing.runtime_checkable
|
||
|
|
class InteractiveContentview(Contentview, typing.Protocol):
|
||
|
|
"""A contentview that prettifies raw data and allows for interactive editing."""
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def reencode(
|
||
|
|
self,
|
||
|
|
prettified: str,
|
||
|
|
metadata: Metadata,
|
||
|
|
) -> bytes:
|
||
|
|
"""
|
||
|
|
Reencode the given (modified) `prettified` output into the original data format.
|
||
|
|
May raise an exception (e.g. `ValueError`) if reencoding failed.
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class Metadata:
|
||
|
|
"""
|
||
|
|
Metadata about the data that is being prettified.
|
||
|
|
|
||
|
|
Do not rely on any given attribute to be present.
|
||
|
|
"""
|
||
|
|
|
||
|
|
flow: Flow | None = None
|
||
|
|
"""The flow that the data belongs to, if any."""
|
||
|
|
|
||
|
|
content_type: str | None = None
|
||
|
|
"""The HTTP content type of the data, if any."""
|
||
|
|
http_message: http.Message | None = None
|
||
|
|
"""The HTTP message that the data belongs to, if any."""
|
||
|
|
tcp_message: tcp.TCPMessage | None = None
|
||
|
|
"""The TCP message that the data belongs to, if any."""
|
||
|
|
udp_message: udp.UDPMessage | None = None
|
||
|
|
"""The UDP message that the data belongs to, if any."""
|
||
|
|
websocket_message: WebSocketMessage | None = None
|
||
|
|
"""The websocket message that the data belongs to, if any."""
|
||
|
|
dns_message: DNSMessage | None = None
|
||
|
|
"""The DNS message that the data belongs to, if any."""
|
||
|
|
|
||
|
|
protobuf_definitions: Path | None = None
|
||
|
|
"""Path to a .proto file that's used to resolve Protobuf field names."""
|
||
|
|
|
||
|
|
original_data: bytes | None = None
|
||
|
|
"""When reencoding: The original data that was prettified."""
|
||
|
|
|
||
|
|
|
||
|
|
Metadata.__init__.__doc__ = "@private"
|