mitmproxy.flow
1import asyncio 2import time 3import uuid 4from typing import Any, ClassVar, Optional 5 6from mitmproxy import connection 7from mitmproxy import exceptions 8from mitmproxy import stateobject 9from mitmproxy import version 10 11 12class Error(stateobject.StateObject): 13 """ 14 An Error. 15 16 This is distinct from an protocol error response (say, a HTTP code 500), 17 which is represented by a normal `mitmproxy.http.Response` object. This class is 18 responsible for indicating errors that fall outside of normal protocol 19 communications, like interrupted connections, timeouts, or protocol errors. 20 """ 21 22 msg: str 23 """Message describing the error.""" 24 25 timestamp: float 26 """Unix timestamp of when this error happened.""" 27 28 KILLED_MESSAGE: ClassVar[str] = "Connection killed." 29 30 def __init__(self, msg: str, timestamp: Optional[float] = None) -> None: 31 """Create an error. If no timestamp is passed, the current time is used.""" 32 self.msg = msg 33 self.timestamp = timestamp or time.time() 34 35 _stateobject_attributes = dict(msg=str, timestamp=float) 36 37 def __str__(self): 38 return self.msg 39 40 def __repr__(self): 41 return self.msg 42 43 @classmethod 44 def from_state(cls, state): 45 # the default implementation assumes an empty constructor. Override 46 # accordingly. 47 f = cls(None) 48 f.set_state(state) 49 return f 50 51 52class Flow(stateobject.StateObject): 53 """ 54 Base class for network flows. A flow is a collection of objects, 55 for example HTTP request/response pairs or a list of TCP messages. 56 57 See also: 58 - mitmproxy.http.HTTPFlow 59 - mitmproxy.tcp.TCPFlow 60 - mitmproxy.udp.UDPFlow 61 """ 62 63 client_conn: connection.Client 64 """The client that connected to mitmproxy.""" 65 66 server_conn: connection.Server 67 """ 68 The server mitmproxy connected to. 69 70 Some flows may never cause mitmproxy to initiate a server connection, 71 for example because their response is replayed by mitmproxy itself. 72 To simplify implementation, those flows will still have a `server_conn` attribute 73 with a `timestamp_start` set to `None`. 74 """ 75 76 error: Optional[Error] = None 77 """A connection or protocol error affecting this flow.""" 78 79 intercepted: bool 80 """ 81 If `True`, the flow is currently paused by mitmproxy. 82 We're waiting for a user action to forward the flow to its destination. 83 """ 84 85 marked: str = "" 86 """ 87 If this attribute is a non-empty string the flow has been marked by the user. 88 89 A string value will be used as the marker annotation. May either be a single character or a Unicode emoji name. 90 91 For example `:grapes:` becomes `🍇` in views that support emoji rendering. 92 Consult the [Github API Emoji List](https://api.github.com/emojis) for a list of emoji that may be used. 93 Not all emoji, especially [emoji modifiers](https://en.wikipedia.org/wiki/Miscellaneous_Symbols_and_Pictographs#Emoji_modifiers) 94 will render consistently. 95 96 The default marker for the view will be used if the Unicode emoji name can not be interpreted. 97 """ 98 99 is_replay: Optional[str] 100 """ 101 This attribute indicates if this flow has been replayed in either direction. 102 103 - a value of `request` indicates that the request has been artifically replayed by mitmproxy to the server. 104 - a value of `response` indicates that the response to the client's request has been set by server replay. 105 """ 106 107 live: bool 108 """ 109 If `True`, the flow belongs to a currently active connection. 110 If `False`, the flow may have been already completed or loaded from disk. 111 """ 112 113 timestamp_created: float 114 """ 115 The Unix timestamp of when this flow was created. 116 117 In contrast to `timestamp_start`, this value will not change when a flow is replayed. 118 """ 119 120 def __init__( 121 self, 122 client_conn: connection.Client, 123 server_conn: connection.Server, 124 live: bool = False, 125 ) -> None: 126 self.id = str(uuid.uuid4()) 127 self.client_conn = client_conn 128 self.server_conn = server_conn 129 self.live = live 130 self.timestamp_created = time.time() 131 132 self.intercepted: bool = False 133 self._resume_event: Optional[asyncio.Event] = None 134 self._backup: Optional[Flow] = None 135 self.marked: str = "" 136 self.is_replay: Optional[str] = None 137 self.metadata: dict[str, Any] = dict() 138 self.comment: str = "" 139 140 _stateobject_attributes = dict( 141 id=str, 142 error=Error, 143 client_conn=connection.Client, 144 server_conn=connection.Server, 145 intercepted=bool, 146 is_replay=str, 147 marked=str, 148 metadata=dict[str, Any], 149 comment=str, 150 timestamp_created=float, 151 ) 152 153 __types: dict[str, type["Flow"]] = {} 154 155 type: ClassVar[str] # automatically derived from the class name in __init_subclass__ 156 """The flow type, for example `http`, `tcp`, or `dns`.""" 157 158 def __init_subclass__(cls, **kwargs): 159 cls.type = cls.__name__.removesuffix("Flow").lower() 160 Flow.__types[cls.type] = cls 161 162 def get_state(self): 163 d = super().get_state() 164 d.update(version=version.FLOW_FORMAT_VERSION, type=self.type) 165 if self._backup and self._backup != d: 166 d.update(backup=self._backup) 167 return d 168 169 def set_state(self, state): 170 state = state.copy() 171 state.pop("version") 172 state.pop("type") 173 if "backup" in state: 174 self._backup = state.pop("backup") 175 super().set_state(state) 176 177 @classmethod 178 def from_state(cls, state): 179 try: 180 flow_cls = Flow.__types[state["type"]] 181 except KeyError: 182 raise ValueError(f"Unknown flow type: {state['type']}") 183 f = flow_cls(None, None) # noqa 184 f.set_state(state) 185 return f 186 187 def copy(self): 188 """Make a copy of this flow.""" 189 f = super().copy() 190 f.live = False 191 return f 192 193 def modified(self): 194 """ 195 `True` if this file has been modified by a user, `False` otherwise. 196 """ 197 if self._backup: 198 return self._backup != self.get_state() 199 else: 200 return False 201 202 def backup(self, force=False): 203 """ 204 Save a backup of this flow, which can be restored by calling `Flow.revert()`. 205 """ 206 if not self._backup: 207 self._backup = self.get_state() 208 209 def revert(self): 210 """ 211 Revert to the last backed up state. 212 """ 213 if self._backup: 214 self.set_state(self._backup) 215 self._backup = None 216 217 @property 218 def killable(self): 219 """*Read-only:* `True` if this flow can be killed, `False` otherwise.""" 220 return self.live and not (self.error and self.error.msg == Error.KILLED_MESSAGE) 221 222 def kill(self): 223 """ 224 Kill this flow. The current request/response will not be forwarded to its destination. 225 """ 226 if not self.killable: 227 raise exceptions.ControlException("Flow is not killable.") 228 # TODO: The way we currently signal killing is not ideal. One major problem is that we cannot kill 229 # flows in transit (https://github.com/mitmproxy/mitmproxy/issues/4711), even though they are advertised 230 # as killable. An alternative approach would be to introduce a `KillInjected` event similar to 231 # `MessageInjected`, which should fix this issue. 232 self.error = Error(Error.KILLED_MESSAGE) 233 self.intercepted = False 234 self.live = False 235 236 def intercept(self): 237 """ 238 Intercept this Flow. Processing will stop until resume is 239 called. 240 """ 241 if self.intercepted: 242 return 243 self.intercepted = True 244 if self._resume_event is not None: 245 self._resume_event.clear() 246 247 async def wait_for_resume(self): 248 """ 249 Wait until this Flow is resumed. 250 """ 251 if not self.intercepted: 252 return 253 if self._resume_event is None: 254 self._resume_event = asyncio.Event() 255 await self._resume_event.wait() 256 257 def resume(self): 258 """ 259 Continue with the flow – called after an intercept(). 260 """ 261 if not self.intercepted: 262 return 263 self.intercepted = False 264 if self._resume_event is not None: 265 self._resume_event.set() 266 267 @property 268 def timestamp_start(self) -> float: 269 """ 270 *Read-only:* Start time of the flow. 271 Depending on the flow type, this property is an alias for 272 `mitmproxy.connection.Client.timestamp_start` or `mitmproxy.http.Request.timestamp_start`. 273 """ 274 return self.client_conn.timestamp_start 275 276 277__all__ = [ 278 "Flow", 279 "Error", 280]
53class Flow(stateobject.StateObject): 54 """ 55 Base class for network flows. A flow is a collection of objects, 56 for example HTTP request/response pairs or a list of TCP messages. 57 58 See also: 59 - mitmproxy.http.HTTPFlow 60 - mitmproxy.tcp.TCPFlow 61 - mitmproxy.udp.UDPFlow 62 """ 63 64 client_conn: connection.Client 65 """The client that connected to mitmproxy.""" 66 67 server_conn: connection.Server 68 """ 69 The server mitmproxy connected to. 70 71 Some flows may never cause mitmproxy to initiate a server connection, 72 for example because their response is replayed by mitmproxy itself. 73 To simplify implementation, those flows will still have a `server_conn` attribute 74 with a `timestamp_start` set to `None`. 75 """ 76 77 error: Optional[Error] = None 78 """A connection or protocol error affecting this flow.""" 79 80 intercepted: bool 81 """ 82 If `True`, the flow is currently paused by mitmproxy. 83 We're waiting for a user action to forward the flow to its destination. 84 """ 85 86 marked: str = "" 87 """ 88 If this attribute is a non-empty string the flow has been marked by the user. 89 90 A string value will be used as the marker annotation. May either be a single character or a Unicode emoji name. 91 92 For example `:grapes:` becomes `🍇` in views that support emoji rendering. 93 Consult the [Github API Emoji List](https://api.github.com/emojis) for a list of emoji that may be used. 94 Not all emoji, especially [emoji modifiers](https://en.wikipedia.org/wiki/Miscellaneous_Symbols_and_Pictographs#Emoji_modifiers) 95 will render consistently. 96 97 The default marker for the view will be used if the Unicode emoji name can not be interpreted. 98 """ 99 100 is_replay: Optional[str] 101 """ 102 This attribute indicates if this flow has been replayed in either direction. 103 104 - a value of `request` indicates that the request has been artifically replayed by mitmproxy to the server. 105 - a value of `response` indicates that the response to the client's request has been set by server replay. 106 """ 107 108 live: bool 109 """ 110 If `True`, the flow belongs to a currently active connection. 111 If `False`, the flow may have been already completed or loaded from disk. 112 """ 113 114 timestamp_created: float 115 """ 116 The Unix timestamp of when this flow was created. 117 118 In contrast to `timestamp_start`, this value will not change when a flow is replayed. 119 """ 120 121 def __init__( 122 self, 123 client_conn: connection.Client, 124 server_conn: connection.Server, 125 live: bool = False, 126 ) -> None: 127 self.id = str(uuid.uuid4()) 128 self.client_conn = client_conn 129 self.server_conn = server_conn 130 self.live = live 131 self.timestamp_created = time.time() 132 133 self.intercepted: bool = False 134 self._resume_event: Optional[asyncio.Event] = None 135 self._backup: Optional[Flow] = None 136 self.marked: str = "" 137 self.is_replay: Optional[str] = None 138 self.metadata: dict[str, Any] = dict() 139 self.comment: str = "" 140 141 _stateobject_attributes = dict( 142 id=str, 143 error=Error, 144 client_conn=connection.Client, 145 server_conn=connection.Server, 146 intercepted=bool, 147 is_replay=str, 148 marked=str, 149 metadata=dict[str, Any], 150 comment=str, 151 timestamp_created=float, 152 ) 153 154 __types: dict[str, type["Flow"]] = {} 155 156 type: ClassVar[str] # automatically derived from the class name in __init_subclass__ 157 """The flow type, for example `http`, `tcp`, or `dns`.""" 158 159 def __init_subclass__(cls, **kwargs): 160 cls.type = cls.__name__.removesuffix("Flow").lower() 161 Flow.__types[cls.type] = cls 162 163 def get_state(self): 164 d = super().get_state() 165 d.update(version=version.FLOW_FORMAT_VERSION, type=self.type) 166 if self._backup and self._backup != d: 167 d.update(backup=self._backup) 168 return d 169 170 def set_state(self, state): 171 state = state.copy() 172 state.pop("version") 173 state.pop("type") 174 if "backup" in state: 175 self._backup = state.pop("backup") 176 super().set_state(state) 177 178 @classmethod 179 def from_state(cls, state): 180 try: 181 flow_cls = Flow.__types[state["type"]] 182 except KeyError: 183 raise ValueError(f"Unknown flow type: {state['type']}") 184 f = flow_cls(None, None) # noqa 185 f.set_state(state) 186 return f 187 188 def copy(self): 189 """Make a copy of this flow.""" 190 f = super().copy() 191 f.live = False 192 return f 193 194 def modified(self): 195 """ 196 `True` if this file has been modified by a user, `False` otherwise. 197 """ 198 if self._backup: 199 return self._backup != self.get_state() 200 else: 201 return False 202 203 def backup(self, force=False): 204 """ 205 Save a backup of this flow, which can be restored by calling `Flow.revert()`. 206 """ 207 if not self._backup: 208 self._backup = self.get_state() 209 210 def revert(self): 211 """ 212 Revert to the last backed up state. 213 """ 214 if self._backup: 215 self.set_state(self._backup) 216 self._backup = None 217 218 @property 219 def killable(self): 220 """*Read-only:* `True` if this flow can be killed, `False` otherwise.""" 221 return self.live and not (self.error and self.error.msg == Error.KILLED_MESSAGE) 222 223 def kill(self): 224 """ 225 Kill this flow. The current request/response will not be forwarded to its destination. 226 """ 227 if not self.killable: 228 raise exceptions.ControlException("Flow is not killable.") 229 # TODO: The way we currently signal killing is not ideal. One major problem is that we cannot kill 230 # flows in transit (https://github.com/mitmproxy/mitmproxy/issues/4711), even though they are advertised 231 # as killable. An alternative approach would be to introduce a `KillInjected` event similar to 232 # `MessageInjected`, which should fix this issue. 233 self.error = Error(Error.KILLED_MESSAGE) 234 self.intercepted = False 235 self.live = False 236 237 def intercept(self): 238 """ 239 Intercept this Flow. Processing will stop until resume is 240 called. 241 """ 242 if self.intercepted: 243 return 244 self.intercepted = True 245 if self._resume_event is not None: 246 self._resume_event.clear() 247 248 async def wait_for_resume(self): 249 """ 250 Wait until this Flow is resumed. 251 """ 252 if not self.intercepted: 253 return 254 if self._resume_event is None: 255 self._resume_event = asyncio.Event() 256 await self._resume_event.wait() 257 258 def resume(self): 259 """ 260 Continue with the flow – called after an intercept(). 261 """ 262 if not self.intercepted: 263 return 264 self.intercepted = False 265 if self._resume_event is not None: 266 self._resume_event.set() 267 268 @property 269 def timestamp_start(self) -> float: 270 """ 271 *Read-only:* Start time of the flow. 272 Depending on the flow type, this property is an alias for 273 `mitmproxy.connection.Client.timestamp_start` or `mitmproxy.http.Request.timestamp_start`. 274 """ 275 return self.client_conn.timestamp_start
Base class for network flows. A flow is a collection of objects, for example HTTP request/response pairs or a list of TCP messages.
See also:
The server mitmproxy connected to.
Some flows may never cause mitmproxy to initiate a server connection,
for example because their response is replayed by mitmproxy itself.
To simplify implementation, those flows will still have a server_conn
attribute
with a timestamp_start
set to None
.
If True
, the flow is currently paused by mitmproxy.
We're waiting for a user action to forward the flow to its destination.
If this attribute is a non-empty string the flow has been marked by the user.
A string value will be used as the marker annotation. May either be a single character or a Unicode emoji name.
For example :grapes:
becomes 🍇
in views that support emoji rendering.
Consult the Github API Emoji List for a list of emoji that may be used.
Not all emoji, especially emoji modifiers
will render consistently.
The default marker for the view will be used if the Unicode emoji name can not be interpreted.
This attribute indicates if this flow has been replayed in either direction.
- a value of
request
indicates that the request has been artifically replayed by mitmproxy to the server. - a value of
response
indicates that the response to the client's request has been set by server replay.
If True
, the flow belongs to a currently active connection.
If False
, the flow may have been already completed or loaded from disk.
The Unix timestamp of when this flow was created.
In contrast to timestamp_start
, this value will not change when a flow is replayed.
188 def copy(self): 189 """Make a copy of this flow.""" 190 f = super().copy() 191 f.live = False 192 return f
Make a copy of this flow.
194 def modified(self): 195 """ 196 `True` if this file has been modified by a user, `False` otherwise. 197 """ 198 if self._backup: 199 return self._backup != self.get_state() 200 else: 201 return False
True
if this file has been modified by a user, False
otherwise.
203 def backup(self, force=False): 204 """ 205 Save a backup of this flow, which can be restored by calling `Flow.revert()`. 206 """ 207 if not self._backup: 208 self._backup = self.get_state()
Save a backup of this flow, which can be restored by calling Flow.revert()
.
210 def revert(self): 211 """ 212 Revert to the last backed up state. 213 """ 214 if self._backup: 215 self.set_state(self._backup) 216 self._backup = None
Revert to the last backed up state.
223 def kill(self): 224 """ 225 Kill this flow. The current request/response will not be forwarded to its destination. 226 """ 227 if not self.killable: 228 raise exceptions.ControlException("Flow is not killable.") 229 # TODO: The way we currently signal killing is not ideal. One major problem is that we cannot kill 230 # flows in transit (https://github.com/mitmproxy/mitmproxy/issues/4711), even though they are advertised 231 # as killable. An alternative approach would be to introduce a `KillInjected` event similar to 232 # `MessageInjected`, which should fix this issue. 233 self.error = Error(Error.KILLED_MESSAGE) 234 self.intercepted = False 235 self.live = False
Kill this flow. The current request/response will not be forwarded to its destination.
237 def intercept(self): 238 """ 239 Intercept this Flow. Processing will stop until resume is 240 called. 241 """ 242 if self.intercepted: 243 return 244 self.intercepted = True 245 if self._resume_event is not None: 246 self._resume_event.clear()
Intercept this Flow. Processing will stop until resume is called.
248 async def wait_for_resume(self): 249 """ 250 Wait until this Flow is resumed. 251 """ 252 if not self.intercepted: 253 return 254 if self._resume_event is None: 255 self._resume_event = asyncio.Event() 256 await self._resume_event.wait()
Wait until this Flow is resumed.
258 def resume(self): 259 """ 260 Continue with the flow – called after an intercept(). 261 """ 262 if not self.intercepted: 263 return 264 self.intercepted = False 265 if self._resume_event is not None: 266 self._resume_event.set()
Continue with the flow – called after an intercept().
Read-only: Start time of the flow.
Depending on the flow type, this property is an alias for
mitmproxy.connection.Client.timestamp_start
or mitmproxy.http.Request.timestamp_start
.
13class Error(stateobject.StateObject): 14 """ 15 An Error. 16 17 This is distinct from an protocol error response (say, a HTTP code 500), 18 which is represented by a normal `mitmproxy.http.Response` object. This class is 19 responsible for indicating errors that fall outside of normal protocol 20 communications, like interrupted connections, timeouts, or protocol errors. 21 """ 22 23 msg: str 24 """Message describing the error.""" 25 26 timestamp: float 27 """Unix timestamp of when this error happened.""" 28 29 KILLED_MESSAGE: ClassVar[str] = "Connection killed." 30 31 def __init__(self, msg: str, timestamp: Optional[float] = None) -> None: 32 """Create an error. If no timestamp is passed, the current time is used.""" 33 self.msg = msg 34 self.timestamp = timestamp or time.time() 35 36 _stateobject_attributes = dict(msg=str, timestamp=float) 37 38 def __str__(self): 39 return self.msg 40 41 def __repr__(self): 42 return self.msg 43 44 @classmethod 45 def from_state(cls, state): 46 # the default implementation assumes an empty constructor. Override 47 # accordingly. 48 f = cls(None) 49 f.set_state(state) 50 return f
An Error.
This is distinct from an protocol error response (say, a HTTP code 500),
which is represented by a normal mitmproxy.http.Response
object. This class is
responsible for indicating errors that fall outside of normal protocol
communications, like interrupted connections, timeouts, or protocol errors.
Inherited Members
- mitmproxy.coretypes.serializable.Serializable
- copy