Edit on GitHub

mitmproxy.contentviews

mitmproxy includes a set of content views which can be used to format/decode/highlight/reencode data. While they are mostly used for HTTP message bodies, the may be used in other contexts, e.g. to decode WebSocket messages.

See "Custom Contentviews" in the mitmproxy documentation for examples.

  1"""
  2mitmproxy includes a set of content views which can be used to
  3format/decode/highlight/reencode data. While they are mostly used for HTTP message
  4bodies, the may be used in other contexts, e.g. to decode WebSocket messages.
  5
  6See "Custom Contentviews" in the mitmproxy documentation for examples.
  7"""
  8
  9import logging
 10import sys
 11import traceback
 12import warnings
 13from dataclasses import dataclass
 14
 15from ..addonmanager import cut_traceback
 16from ._api import Contentview
 17from ._api import InteractiveContentview
 18from ._api import Metadata
 19from ._api import SyntaxHighlight
 20from ._compat import get  # noqa: F401
 21from ._compat import LegacyContentview
 22from ._compat import remove  # noqa: F401
 23from ._registry import ContentviewRegistry
 24from ._utils import ContentviewMessage
 25from ._utils import get_data
 26from ._utils import make_metadata
 27from ._view_css import css
 28from ._view_dns import dns
 29from ._view_graphql import graphql
 30from ._view_http3 import http3
 31from ._view_image import image
 32from ._view_javascript import javascript
 33from ._view_json import json_view
 34from ._view_mqtt import mqtt
 35from ._view_multipart import multipart
 36from ._view_query import query
 37from ._view_raw import raw
 38from ._view_socketio import socket_io
 39from ._view_urlencoded import urlencoded
 40from ._view_wbxml import wbxml
 41from ._view_xml_html import xml_html
 42from ._view_zip import zip
 43from .base import View
 44import mitmproxy_rs.contentviews
 45from mitmproxy import flow
 46from mitmproxy.utils import strutils
 47
 48logger = logging.getLogger(__name__)
 49
 50
 51@dataclass
 52class ContentviewResult:
 53    text: str
 54    syntax_highlight: SyntaxHighlight
 55    view_name: str | None
 56    description: str
 57
 58
 59registry = ContentviewRegistry()
 60
 61
 62def prettify_message(
 63    message: ContentviewMessage,
 64    flow: flow.Flow,
 65    view_name: str = "auto",
 66    registry: ContentviewRegistry = registry,
 67) -> ContentviewResult:
 68    data, enc = get_data(message)
 69    if data is None:
 70        return ContentviewResult(
 71            text="Content is missing.",
 72            syntax_highlight="error",
 73            description="",
 74            view_name=None,
 75        )
 76
 77    # Determine the correct view
 78    metadata = make_metadata(message, flow)
 79    view = registry.get_view(data, metadata, view_name)
 80
 81    # Finally, we can pretty-print!
 82    try:
 83        ret = ContentviewResult(
 84            text=view.prettify(data, metadata),
 85            syntax_highlight=view.syntax_highlight,
 86            view_name=view.name,
 87            description=enc,
 88        )
 89    except Exception as e:
 90        logger.debug(f"Contentview {view.name!r} failed: {e}", exc_info=True)
 91        if view_name == "auto":
 92            # If the contentview was chosen as the best matching one, fall back to raw.
 93            ret = ContentviewResult(
 94                text=raw.prettify(data, metadata),
 95                syntax_highlight=raw.syntax_highlight,
 96                view_name=raw.name,
 97                description=f"{enc}[failed to parse as {view.name}]",
 98            )
 99        else:
100            # Cut the exception traceback for display.
101            exc, value, tb = sys.exc_info()
102            tb_cut = cut_traceback(tb, "prettify_message")
103            if (
104                tb_cut == tb
105            ):  # If there are no extra frames, just skip displaying the traceback.
106                tb_cut = None
107            # If the contentview has been set explicitly, we display a hard error.
108            err = "".join(traceback.format_exception(exc, value=value, tb=tb_cut))
109            ret = ContentviewResult(
110                text=f"Couldn't parse as {view.name}:\n{err}",
111                syntax_highlight="error",
112                view_name=view.name,
113                description=enc,
114            )
115
116    ret.text = strutils.escape_control_characters(ret.text)
117    return ret
118
119
120def reencode_message(
121    prettified: str,
122    message: ContentviewMessage,
123    flow: flow.Flow,
124    view_name: str,
125) -> bytes:
126    metadata = make_metadata(message, flow)
127    view = registry[view_name.lower()]
128    if not isinstance(view, InteractiveContentview):
129        raise ValueError(f"Contentview {view.name} is not interactive.")
130    return view.reencode(prettified, metadata)
131
132
133_views: list[Contentview] = [
134    css,
135    dns,
136    graphql,
137    http3,
138    image,
139    javascript,
140    json_view,
141    mqtt,
142    multipart,
143    query,
144    raw,
145    socket_io,
146    urlencoded,
147    wbxml,
148    xml_html,
149    zip,
150]
151for view in _views:
152    registry.register(view)
153for name in mitmproxy_rs.contentviews.__all__:
154    if name.startswith("_"):
155        continue
156    cv = getattr(mitmproxy_rs.contentviews, name)
157    if isinstance(cv, Contentview) and not isinstance(cv, type):
158        registry.register(cv)
159
160
161def add(contentview: Contentview | type[Contentview]) -> None:
162    """
163    Register a contentview for use in mitmproxy.
164
165    You may pass a `Contentview` instance or the class itself.
166    When passing the class, its constructor will be invoked with no arguments.
167    """
168    if isinstance(contentview, View):
169        warnings.warn(
170            f"`mitmproxy.contentviews.View` is deprecated since mitmproxy 12, "
171            f"migrate {contentview.__class__.__name__} to `mitmproxy.contentviews.Contentview` instead.",
172            stacklevel=2,
173        )
174        contentview = LegacyContentview(contentview)
175    registry.register(contentview)
176
177
178# hack: docstring where pdoc finds it.
179SyntaxHighlight = SyntaxHighlight
180"""
181Syntax highlighting formats currently supported by mitmproxy.
182Note that YAML is a superset of JSON; so if you'd like to highlight JSON, pick the YAML highlighter.
183
184*If you have a concrete use case for additional formats, please open an issue.*
185"""
186
187
188__all__ = [
189    # Public Contentview API
190    "Contentview",
191    "InteractiveContentview",
192    "SyntaxHighlight",
193    "add",
194    "Metadata",
195]
@typing.runtime_checkable
class Contentview(typing.Protocol):
24@typing.runtime_checkable
25class Contentview(typing.Protocol):
26    """
27    Base class for all contentviews.
28    """
29
30    @property
31    def name(self) -> str:
32        """
33        The name of this contentview, e.g. "XML/HTML".
34        Inferred from the class name by default.
35        """
36        return type(self).__name__.removesuffix("Contentview")
37
38    @property
39    def syntax_highlight(self) -> SyntaxHighlight:
40        """Optional syntax highlighting that should be applied to the prettified output."""
41        return "none"
42
43    @abstractmethod
44    def prettify(
45        self,
46        data: bytes,
47        metadata: Metadata,
48    ) -> str:
49        """
50        Transform raw data into human-readable output.
51        May raise an exception (e.g. `ValueError`) if data cannot be prettified.
52        """
53
54    def render_priority(
55        self,
56        data: bytes,
57        metadata: Metadata,
58    ) -> float:
59        """
60        Return the priority of this view for rendering `data`.
61        If no particular view is chosen by the user, the view with the highest priority is selected.
62        If this view does not support the given data, return a float < 0.
63        """
64        return 0
65
66    def __lt__(self, other):
67        return self.name.__lt__(other.name)

Base class for all contentviews.

name: str
30    @property
31    def name(self) -> str:
32        """
33        The name of this contentview, e.g. "XML/HTML".
34        Inferred from the class name by default.
35        """
36        return type(self).__name__.removesuffix("Contentview")

The name of this contentview, e.g. "XML/HTML". Inferred from the class name by default.

syntax_highlight: SyntaxHighlight
38    @property
39    def syntax_highlight(self) -> SyntaxHighlight:
40        """Optional syntax highlighting that should be applied to the prettified output."""
41        return "none"

Optional syntax highlighting that should be applied to the prettified output.

@abstractmethod
def prettify(self, data: bytes, metadata: Metadata) -> str:
43    @abstractmethod
44    def prettify(
45        self,
46        data: bytes,
47        metadata: Metadata,
48    ) -> str:
49        """
50        Transform raw data into human-readable output.
51        May raise an exception (e.g. `ValueError`) if data cannot be prettified.
52        """

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:
54    def render_priority(
55        self,
56        data: bytes,
57        metadata: Metadata,
58    ) -> float:
59        """
60        Return the priority of this view for rendering `data`.
61        If no particular view is chosen by the user, the view with the highest priority is selected.
62        If this view does not support the given data, return a float < 0.
63        """
64        return 0

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.

@typing.runtime_checkable
class InteractiveContentview(mitmproxy.contentviews.Contentview, typing.Protocol):
70@typing.runtime_checkable
71class InteractiveContentview(Contentview, typing.Protocol):
72    """A contentview that prettifies raw data and allows for interactive editing."""
73
74    @abstractmethod
75    def reencode(
76        self,
77        prettified: str,
78        metadata: Metadata,
79    ) -> bytes:
80        """
81        Reencode the given (modified) `prettified` output into the original data format.
82        May raise an exception (e.g. `ValueError`) if reencoding failed.
83        """

A contentview that prettifies raw data and allows for interactive editing.

@abstractmethod
def reencode( self, prettified: str, metadata: Metadata) -> bytes:
74    @abstractmethod
75    def reencode(
76        self,
77        prettified: str,
78        metadata: Metadata,
79    ) -> bytes:
80        """
81        Reencode the given (modified) `prettified` output into the original data format.
82        May raise an exception (e.g. `ValueError`) if reencoding failed.
83        """

Reencode the given (modified) prettified output into the original data format. May raise an exception (e.g. ValueError) if reencoding failed.

type SyntaxHighlight = Literal['css', 'javascript', 'xml', 'yaml', 'none', 'error']

Syntax highlighting formats currently supported by mitmproxy. Note that YAML is a superset of JSON; so if you'd like to highlight JSON, pick the YAML highlighter.

If you have a concrete use case for additional formats, please open an issue.

def add( contentview: Contentview | type[Contentview]) -> None:
162def add(contentview: Contentview | type[Contentview]) -> None:
163    """
164    Register a contentview for use in mitmproxy.
165
166    You may pass a `Contentview` instance or the class itself.
167    When passing the class, its constructor will be invoked with no arguments.
168    """
169    if isinstance(contentview, View):
170        warnings.warn(
171            f"`mitmproxy.contentviews.View` is deprecated since mitmproxy 12, "
172            f"migrate {contentview.__class__.__name__} to `mitmproxy.contentviews.Contentview` instead.",
173            stacklevel=2,
174        )
175        contentview = LegacyContentview(contentview)
176    registry.register(contentview)

Register a contentview for use in mitmproxy.

You may pass a Contentview instance or the class itself. When passing the class, its constructor will be invoked with no arguments.

@dataclass
class Metadata:
 86@dataclass
 87class Metadata:
 88    """
 89    Metadata about the data that is being prettified.
 90
 91    Do not rely on any given attribute to be present.
 92    """
 93
 94    flow: Flow | None = None
 95    """The flow that the data belongs to, if any."""
 96
 97    content_type: str | None = None
 98    """The HTTP content type of the data, if any."""
 99    http_message: http.Message | None = None
100    """The HTTP message that the data belongs to, if any."""
101    tcp_message: tcp.TCPMessage | None = None
102    """The TCP message that the data belongs to, if any."""
103    udp_message: udp.UDPMessage | None = None
104    """The UDP message that the data belongs to, if any."""
105    websocket_message: WebSocketMessage | None = None
106    """The websocket message that the data belongs to, if any."""
107    dns_message: DNSMessage | None = None
108    """The DNS message that the data belongs to, if any."""
109
110    protobuf_definitions: Path | None = None
111    """Path to a .proto file that's used to resolve Protobuf field names."""
112
113    original_data: bytes | None = None
114    """When reencoding: The original data that was prettified."""

Metadata about the data that is being prettified.

Do not rely on any given attribute to be present.

flow: mitmproxy.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: mitmproxy.http.Message | None = None

The HTTP message that the data belongs to, if any.

tcp_message: mitmproxy.tcp.TCPMessage | None = None

The TCP message that the data belongs to, if any.

udp_message: mitmproxy.udp.UDPMessage | None = None

The UDP message that the data belongs to, if any.

websocket_message: mitmproxy.websocket.WebSocketMessage | None = None

The websocket message that the data belongs to, if any.

dns_message: mitmproxy.dns.DNSMessage | None = None

The DNS message that the data belongs to, if any.

protobuf_definitions: pathlib.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.

built with pdocpdoc logo