exp-mitmproxy 12.2.3.post1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. exp_mitmproxy-12.2.3.post1.dist-info/METADATA +120 -0
  2. exp_mitmproxy-12.2.3.post1.dist-info/RECORD +291 -0
  3. exp_mitmproxy-12.2.3.post1.dist-info/WHEEL +5 -0
  4. exp_mitmproxy-12.2.3.post1.dist-info/entry_points.txt +7 -0
  5. exp_mitmproxy-12.2.3.post1.dist-info/licenses/LICENSE +19 -0
  6. exp_mitmproxy-12.2.3.post1.dist-info/top_level.txt +1 -0
  7. mitmproxy/__init__.py +0 -0
  8. mitmproxy/addonmanager.py +308 -0
  9. mitmproxy/addons/__init__.py +67 -0
  10. mitmproxy/addons/anticache.py +18 -0
  11. mitmproxy/addons/anticomp.py +15 -0
  12. mitmproxy/addons/asgiapp.py +144 -0
  13. mitmproxy/addons/block.py +48 -0
  14. mitmproxy/addons/blocklist.py +81 -0
  15. mitmproxy/addons/browser.py +186 -0
  16. mitmproxy/addons/clientplayback.py +298 -0
  17. mitmproxy/addons/command_history.py +96 -0
  18. mitmproxy/addons/comment.py +19 -0
  19. mitmproxy/addons/core.py +286 -0
  20. mitmproxy/addons/cut.py +176 -0
  21. mitmproxy/addons/disable_h2c.py +42 -0
  22. mitmproxy/addons/dns_resolver.py +182 -0
  23. mitmproxy/addons/dumper.py +427 -0
  24. mitmproxy/addons/errorcheck.py +55 -0
  25. mitmproxy/addons/eventstore.py +56 -0
  26. mitmproxy/addons/export.py +232 -0
  27. mitmproxy/addons/intercept.py +63 -0
  28. mitmproxy/addons/keepserving.py +54 -0
  29. mitmproxy/addons/maplocal.py +151 -0
  30. mitmproxy/addons/mapremote.py +68 -0
  31. mitmproxy/addons/modifybody.py +86 -0
  32. mitmproxy/addons/modifyheaders.py +117 -0
  33. mitmproxy/addons/next_layer.py +474 -0
  34. mitmproxy/addons/onboarding.py +34 -0
  35. mitmproxy/addons/onboardingapp/__init__.py +63 -0
  36. mitmproxy/addons/onboardingapp/static/bootstrap.min.css +6 -0
  37. mitmproxy/addons/onboardingapp/static/images/favicon.ico +0 -0
  38. mitmproxy/addons/onboardingapp/static/images/mitmproxy-long.png +0 -0
  39. mitmproxy/addons/onboardingapp/static/mitmproxy.css +44 -0
  40. mitmproxy/addons/onboardingapp/templates/icons/android-brands.svg +1 -0
  41. mitmproxy/addons/onboardingapp/templates/icons/apple-brands.svg +1 -0
  42. mitmproxy/addons/onboardingapp/templates/icons/certificate-solid.svg +1 -0
  43. mitmproxy/addons/onboardingapp/templates/icons/firefox-browser-brands.svg +1 -0
  44. mitmproxy/addons/onboardingapp/templates/icons/linux-brands.svg +1 -0
  45. mitmproxy/addons/onboardingapp/templates/icons/windows-brands.svg +1 -0
  46. mitmproxy/addons/onboardingapp/templates/index.html +143 -0
  47. mitmproxy/addons/onboardingapp/templates/layout.html +29 -0
  48. mitmproxy/addons/proxyauth.py +298 -0
  49. mitmproxy/addons/proxyserver.py +396 -0
  50. mitmproxy/addons/readfile.py +98 -0
  51. mitmproxy/addons/save.py +197 -0
  52. mitmproxy/addons/savehar.py +312 -0
  53. mitmproxy/addons/script.py +229 -0
  54. mitmproxy/addons/server_side_events.py +23 -0
  55. mitmproxy/addons/serverplayback.py +305 -0
  56. mitmproxy/addons/stickyauth.py +38 -0
  57. mitmproxy/addons/stickycookie.py +97 -0
  58. mitmproxy/addons/strip_dns_https_records.py +37 -0
  59. mitmproxy/addons/termlog.py +50 -0
  60. mitmproxy/addons/tlsconfig.py +654 -0
  61. mitmproxy/addons/update_alt_svc.py +33 -0
  62. mitmproxy/addons/upstream_auth.py +62 -0
  63. mitmproxy/addons/view.py +749 -0
  64. mitmproxy/certs.py +741 -0
  65. mitmproxy/command.py +342 -0
  66. mitmproxy/command_lexer.py +41 -0
  67. mitmproxy/connection.py +359 -0
  68. mitmproxy/contentviews/__init__.py +195 -0
  69. mitmproxy/contentviews/_api.py +116 -0
  70. mitmproxy/contentviews/_compat.py +74 -0
  71. mitmproxy/contentviews/_registry.py +73 -0
  72. mitmproxy/contentviews/_utils.py +106 -0
  73. mitmproxy/contentviews/_view_css.py +76 -0
  74. mitmproxy/contentviews/_view_dns.py +53 -0
  75. mitmproxy/contentviews/_view_graphql.py +72 -0
  76. mitmproxy/contentviews/_view_http3.py +153 -0
  77. mitmproxy/contentviews/_view_image/__init__.py +3 -0
  78. mitmproxy/contentviews/_view_image/image_parser.py +119 -0
  79. mitmproxy/contentviews/_view_image/view.py +57 -0
  80. mitmproxy/contentviews/_view_javascript.py +68 -0
  81. mitmproxy/contentviews/_view_json.py +31 -0
  82. mitmproxy/contentviews/_view_mqtt.py +277 -0
  83. mitmproxy/contentviews/_view_multipart.py +32 -0
  84. mitmproxy/contentviews/_view_query.py +31 -0
  85. mitmproxy/contentviews/_view_raw.py +17 -0
  86. mitmproxy/contentviews/_view_socketio.py +98 -0
  87. mitmproxy/contentviews/_view_urlencoded.py +33 -0
  88. mitmproxy/contentviews/_view_wbxml.py +25 -0
  89. mitmproxy/contentviews/_view_xml_html.py +278 -0
  90. mitmproxy/contentviews/_view_zip.py +22 -0
  91. mitmproxy/contentviews/base.py +129 -0
  92. mitmproxy/contrib/README.md +4 -0
  93. mitmproxy/contrib/__init__.py +0 -0
  94. mitmproxy/contrib/click/LICENSE.BSD-3 +28 -0
  95. mitmproxy/contrib/click/__init__.py +159 -0
  96. mitmproxy/contrib/imghdr.py +142 -0
  97. mitmproxy/contrib/kaitaistruct/LICENSE +1 -0
  98. mitmproxy/contrib/kaitaistruct/README.md +3 -0
  99. mitmproxy/contrib/kaitaistruct/__init__.py +0 -0
  100. mitmproxy/contrib/kaitaistruct/dtls_client_hello.ksy +140 -0
  101. mitmproxy/contrib/kaitaistruct/dtls_client_hello.py +202 -0
  102. mitmproxy/contrib/kaitaistruct/exif.py +654 -0
  103. mitmproxy/contrib/kaitaistruct/gif.py +357 -0
  104. mitmproxy/contrib/kaitaistruct/google_protobuf.py +126 -0
  105. mitmproxy/contrib/kaitaistruct/ico.py +93 -0
  106. mitmproxy/contrib/kaitaistruct/jpeg.py +258 -0
  107. mitmproxy/contrib/kaitaistruct/make.sh +14 -0
  108. mitmproxy/contrib/kaitaistruct/png.py +527 -0
  109. mitmproxy/contrib/kaitaistruct/tls_client_hello.ksy +132 -0
  110. mitmproxy/contrib/kaitaistruct/tls_client_hello.py +189 -0
  111. mitmproxy/contrib/kaitaistruct/vlq_base128_le.py +115 -0
  112. mitmproxy/contrib/wbxml/ASCommandResponse.py +73 -0
  113. mitmproxy/contrib/wbxml/ASWBXML.py +903 -0
  114. mitmproxy/contrib/wbxml/ASWBXMLByteQueue.py +102 -0
  115. mitmproxy/contrib/wbxml/ASWBXMLCodePage.py +52 -0
  116. mitmproxy/contrib/wbxml/GlobalTokens.py +50 -0
  117. mitmproxy/contrib/wbxml/InvalidDataException.py +31 -0
  118. mitmproxy/contrib/wbxml/__init__.py +0 -0
  119. mitmproxy/coretypes/__init__.py +0 -0
  120. mitmproxy/coretypes/bidi.py +26 -0
  121. mitmproxy/coretypes/multidict.py +206 -0
  122. mitmproxy/coretypes/serializable.py +199 -0
  123. mitmproxy/ctx.py +14 -0
  124. mitmproxy/dns.py +588 -0
  125. mitmproxy/eventsequence.py +84 -0
  126. mitmproxy/exceptions.py +51 -0
  127. mitmproxy/flow.py +288 -0
  128. mitmproxy/flowfilter.py +705 -0
  129. mitmproxy/hooks.py +95 -0
  130. mitmproxy/http.py +1295 -0
  131. mitmproxy/io/__init__.py +6 -0
  132. mitmproxy/io/compat.py +528 -0
  133. mitmproxy/io/har.py +159 -0
  134. mitmproxy/io/io.py +114 -0
  135. mitmproxy/io/tnetstring.py +261 -0
  136. mitmproxy/log.py +246 -0
  137. mitmproxy/master.py +145 -0
  138. mitmproxy/net/__init__.py +0 -0
  139. mitmproxy/net/check.py +43 -0
  140. mitmproxy/net/dns/__init__.py +0 -0
  141. mitmproxy/net/dns/classes.py +19 -0
  142. mitmproxy/net/dns/domain_names.py +169 -0
  143. mitmproxy/net/dns/https_records.py +134 -0
  144. mitmproxy/net/dns/op_codes.py +27 -0
  145. mitmproxy/net/dns/response_codes.py +58 -0
  146. mitmproxy/net/dns/types.py +193 -0
  147. mitmproxy/net/encoding.py +239 -0
  148. mitmproxy/net/free_port.py +25 -0
  149. mitmproxy/net/http/__init__.py +0 -0
  150. mitmproxy/net/http/cookies.py +387 -0
  151. mitmproxy/net/http/headers.py +113 -0
  152. mitmproxy/net/http/http1/__init__.py +21 -0
  153. mitmproxy/net/http/http1/assemble.py +99 -0
  154. mitmproxy/net/http/http1/read.py +308 -0
  155. mitmproxy/net/http/multipart.py +95 -0
  156. mitmproxy/net/http/status_codes.py +146 -0
  157. mitmproxy/net/http/url.py +200 -0
  158. mitmproxy/net/http/user_agents.py +60 -0
  159. mitmproxy/net/http/validate.py +141 -0
  160. mitmproxy/net/local_ip.py +40 -0
  161. mitmproxy/net/server_spec.py +85 -0
  162. mitmproxy/net/tls.py +330 -0
  163. mitmproxy/options.py +249 -0
  164. mitmproxy/optmanager.py +637 -0
  165. mitmproxy/platform/__init__.py +40 -0
  166. mitmproxy/platform/linux.py +33 -0
  167. mitmproxy/platform/openbsd.py +2 -0
  168. mitmproxy/platform/osx.py +39 -0
  169. mitmproxy/platform/pf.py +42 -0
  170. mitmproxy/platform/windows.py +599 -0
  171. mitmproxy/proxy/__init__.py +20 -0
  172. mitmproxy/proxy/commands.py +159 -0
  173. mitmproxy/proxy/context.py +58 -0
  174. mitmproxy/proxy/events.py +138 -0
  175. mitmproxy/proxy/layer.py +340 -0
  176. mitmproxy/proxy/layers/__init__.py +27 -0
  177. mitmproxy/proxy/layers/dns.py +190 -0
  178. mitmproxy/proxy/layers/http/__init__.py +1210 -0
  179. mitmproxy/proxy/layers/http/_base.py +61 -0
  180. mitmproxy/proxy/layers/http/_events.py +167 -0
  181. mitmproxy/proxy/layers/http/_hooks.py +122 -0
  182. mitmproxy/proxy/layers/http/_http1.py +502 -0
  183. mitmproxy/proxy/layers/http/_http2.py +714 -0
  184. mitmproxy/proxy/layers/http/_http3.py +309 -0
  185. mitmproxy/proxy/layers/http/_http_h2.py +207 -0
  186. mitmproxy/proxy/layers/http/_http_h3.py +321 -0
  187. mitmproxy/proxy/layers/http/_upstream_proxy.py +105 -0
  188. mitmproxy/proxy/layers/modes.py +303 -0
  189. mitmproxy/proxy/layers/quic/__init__.py +41 -0
  190. mitmproxy/proxy/layers/quic/_client_hello_parser.py +111 -0
  191. mitmproxy/proxy/layers/quic/_commands.py +92 -0
  192. mitmproxy/proxy/layers/quic/_events.py +70 -0
  193. mitmproxy/proxy/layers/quic/_hooks.py +77 -0
  194. mitmproxy/proxy/layers/quic/_raw_layers.py +433 -0
  195. mitmproxy/proxy/layers/quic/_stream_layers.py +638 -0
  196. mitmproxy/proxy/layers/tcp.py +143 -0
  197. mitmproxy/proxy/layers/tls.py +692 -0
  198. mitmproxy/proxy/layers/udp.py +132 -0
  199. mitmproxy/proxy/layers/websocket.py +272 -0
  200. mitmproxy/proxy/mode_servers.py +546 -0
  201. mitmproxy/proxy/mode_specs.py +336 -0
  202. mitmproxy/proxy/server.py +619 -0
  203. mitmproxy/proxy/server_hooks.py +76 -0
  204. mitmproxy/proxy/tunnel.py +201 -0
  205. mitmproxy/proxy/utils.py +68 -0
  206. mitmproxy/py.typed +0 -0
  207. mitmproxy/script/__init__.py +5 -0
  208. mitmproxy/script/concurrent.py +32 -0
  209. mitmproxy/tcp.py +75 -0
  210. mitmproxy/test/taddons.py +82 -0
  211. mitmproxy/test/tflow.py +300 -0
  212. mitmproxy/test/tutils.py +95 -0
  213. mitmproxy/tls.py +152 -0
  214. mitmproxy/tools/__init__.py +0 -0
  215. mitmproxy/tools/cmdline.py +152 -0
  216. mitmproxy/tools/console/__init__.py +3 -0
  217. mitmproxy/tools/console/commander/__init__.py +0 -0
  218. mitmproxy/tools/console/commander/commander.py +260 -0
  219. mitmproxy/tools/console/commandexecutor.py +35 -0
  220. mitmproxy/tools/console/commands.py +156 -0
  221. mitmproxy/tools/console/common.py +863 -0
  222. mitmproxy/tools/console/consoleaddons.py +763 -0
  223. mitmproxy/tools/console/defaultkeys.py +277 -0
  224. mitmproxy/tools/console/eventlog.py +63 -0
  225. mitmproxy/tools/console/flowdetailview.py +149 -0
  226. mitmproxy/tools/console/flowlist.py +108 -0
  227. mitmproxy/tools/console/flowview.py +497 -0
  228. mitmproxy/tools/console/grideditor/__init__.py +29 -0
  229. mitmproxy/tools/console/grideditor/base.py +469 -0
  230. mitmproxy/tools/console/grideditor/col_bytes.py +49 -0
  231. mitmproxy/tools/console/grideditor/col_subgrid.py +40 -0
  232. mitmproxy/tools/console/grideditor/col_text.py +48 -0
  233. mitmproxy/tools/console/grideditor/col_viewany.py +34 -0
  234. mitmproxy/tools/console/grideditor/editors.py +220 -0
  235. mitmproxy/tools/console/help.py +113 -0
  236. mitmproxy/tools/console/keybindings.py +163 -0
  237. mitmproxy/tools/console/keymap.py +262 -0
  238. mitmproxy/tools/console/layoutwidget.py +40 -0
  239. mitmproxy/tools/console/master.py +263 -0
  240. mitmproxy/tools/console/options.py +272 -0
  241. mitmproxy/tools/console/overlay.py +195 -0
  242. mitmproxy/tools/console/palettes.py +546 -0
  243. mitmproxy/tools/console/quickhelp.py +191 -0
  244. mitmproxy/tools/console/searchable.py +89 -0
  245. mitmproxy/tools/console/signals.py +66 -0
  246. mitmproxy/tools/console/statusbar.py +366 -0
  247. mitmproxy/tools/console/tabs.py +58 -0
  248. mitmproxy/tools/console/window.py +318 -0
  249. mitmproxy/tools/dump.py +26 -0
  250. mitmproxy/tools/main.py +174 -0
  251. mitmproxy/tools/web/__init__.py +3 -0
  252. mitmproxy/tools/web/app.py +939 -0
  253. mitmproxy/tools/web/index.html +16 -0
  254. mitmproxy/tools/web/master.py +122 -0
  255. mitmproxy/tools/web/static/favicon.ico +0 -0
  256. mitmproxy/tools/web/static/fontawesome-webfont-B-jkhYfk.woff2 +0 -0
  257. mitmproxy/tools/web/static/fontawesome-webfont-CDK5bt4p.woff +0 -0
  258. mitmproxy/tools/web/static/fontawesome-webfont-CQDK8MU3.ttf +0 -0
  259. mitmproxy/tools/web/static/fontawesome-webfont-D13rzr4g.svg +2671 -0
  260. mitmproxy/tools/web/static/fontawesome-webfont-G5YE5S7X.eot +0 -0
  261. mitmproxy/tools/web/static/index-Be7e-cwP.js +10 -0
  262. mitmproxy/tools/web/static/index-DhPPoJ7G.css +1 -0
  263. mitmproxy/tools/web/static/vendor-BS4xPthR.js +62 -0
  264. mitmproxy/tools/web/static/vendor-Cg3S-P9H.css +1 -0
  265. mitmproxy/tools/web/static_viewer.py +118 -0
  266. mitmproxy/tools/web/templates/login.html +35 -0
  267. mitmproxy/tools/web/web_columns.py +15 -0
  268. mitmproxy/tools/web/webaddons.py +151 -0
  269. mitmproxy/types.py +497 -0
  270. mitmproxy/udp.py +72 -0
  271. mitmproxy/utils/__init__.py +0 -0
  272. mitmproxy/utils/arg_check.py +167 -0
  273. mitmproxy/utils/asyncio_utils.py +100 -0
  274. mitmproxy/utils/data.py +37 -0
  275. mitmproxy/utils/debug.py +131 -0
  276. mitmproxy/utils/emoji.py +1893 -0
  277. mitmproxy/utils/htpasswd.py +81 -0
  278. mitmproxy/utils/human.py +99 -0
  279. mitmproxy/utils/magisk.py +112 -0
  280. mitmproxy/utils/pyinstaller/__init__.py +7 -0
  281. mitmproxy/utils/pyinstaller/hook-mitmproxy.addons.onboardingapp.py +3 -0
  282. mitmproxy/utils/pyinstaller/hook-mitmproxy.py +1 -0
  283. mitmproxy/utils/pyinstaller/hook-mitmproxy.tools.web.py +3 -0
  284. mitmproxy/utils/signals.py +137 -0
  285. mitmproxy/utils/sliding_window.py +32 -0
  286. mitmproxy/utils/spec.py +21 -0
  287. mitmproxy/utils/strutils.py +289 -0
  288. mitmproxy/utils/typecheck.py +72 -0
  289. mitmproxy/utils/vt_codes.py +60 -0
  290. mitmproxy/version.py +54 -0
  291. mitmproxy/websocket.py +182 -0
@@ -0,0 +1,308 @@
1
+ import contextlib
2
+ import inspect
3
+ import logging
4
+ import pprint
5
+ import sys
6
+ import traceback
7
+ import types
8
+ from collections.abc import Callable
9
+ from collections.abc import Sequence
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+
13
+ from mitmproxy import exceptions
14
+ from mitmproxy import flow
15
+ from mitmproxy import hooks
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def _get_name(itm):
21
+ return getattr(itm, "name", itm.__class__.__name__.lower())
22
+
23
+
24
+ def cut_traceback(tb, func_name):
25
+ """
26
+ Cut off a traceback at the function with the given name.
27
+ The func_name's frame is excluded.
28
+
29
+ Args:
30
+ tb: traceback object, as returned by sys.exc_info()[2]
31
+ func_name: function name
32
+
33
+ Returns:
34
+ Reduced traceback.
35
+ """
36
+ tb_orig = tb
37
+ for _, _, fname, _ in traceback.extract_tb(tb):
38
+ tb = tb.tb_next
39
+ if fname == func_name:
40
+ break
41
+ return tb or tb_orig
42
+
43
+
44
+ @contextlib.contextmanager
45
+ def safecall():
46
+ try:
47
+ yield
48
+ except (exceptions.AddonHalt, exceptions.OptionsError):
49
+ raise
50
+ except Exception:
51
+ etype, value, tb = sys.exc_info()
52
+ tb = cut_traceback(tb, "invoke_addon_sync")
53
+ tb = cut_traceback(tb, "invoke_addon")
54
+ assert etype
55
+ assert value
56
+ logger.error(
57
+ f"Addon error: {value}",
58
+ exc_info=(etype, value, tb),
59
+ )
60
+
61
+
62
+ class Loader:
63
+ """
64
+ A loader object is passed to the load() event when addons start up.
65
+ """
66
+
67
+ def __init__(self, master):
68
+ self.master = master
69
+
70
+ def add_option(
71
+ self,
72
+ name: str,
73
+ typespec: type,
74
+ default: Any,
75
+ help: str,
76
+ choices: Sequence[str] | None = None,
77
+ ) -> None:
78
+ """
79
+ Add an option to mitmproxy.
80
+
81
+ Help should be a single paragraph with no linebreaks - it will be
82
+ reflowed by tools. Information on the data type should be omitted -
83
+ it will be generated and added by tools as needed.
84
+ """
85
+ assert not isinstance(choices, str)
86
+ if name in self.master.options:
87
+ existing = self.master.options._options[name]
88
+ same_signature = (
89
+ existing.name == name
90
+ and existing.typespec == typespec
91
+ and existing.default == default
92
+ and existing.help == help
93
+ and existing.choices == choices
94
+ )
95
+ if same_signature:
96
+ return
97
+ else:
98
+ logger.warning("Over-riding existing option %s" % name)
99
+ self.master.options.add_option(name, typespec, default, help, choices)
100
+
101
+ def add_command(self, path: str, func: Callable) -> None:
102
+ """Add a command to mitmproxy.
103
+
104
+ Unless you are generating commands programatically,
105
+ this API should be avoided. Decorate your function with `@mitmproxy.command.command` instead.
106
+ """
107
+ self.master.commands.add(path, func)
108
+
109
+
110
+ def traverse(chain):
111
+ """
112
+ Recursively traverse an addon chain.
113
+ """
114
+ for a in chain:
115
+ yield a
116
+ if hasattr(a, "addons"):
117
+ yield from traverse(a.addons)
118
+
119
+
120
+ @dataclass
121
+ class LoadHook(hooks.Hook):
122
+ """
123
+ Called when an addon is first loaded. This event receives a Loader
124
+ object, which contains methods for adding options and commands. This
125
+ method is where the addon configures itself.
126
+ """
127
+
128
+ loader: Loader
129
+
130
+
131
+ class AddonManager:
132
+ def __init__(self, master):
133
+ self.lookup = {}
134
+ self.chain = []
135
+ self.master = master
136
+ master.options.changed.connect(self._configure_all)
137
+
138
+ def _configure_all(self, updated):
139
+ self.trigger(hooks.ConfigureHook(updated))
140
+
141
+ def clear(self):
142
+ """
143
+ Remove all addons.
144
+ """
145
+ for a in self.chain:
146
+ self.invoke_addon_sync(a, hooks.DoneHook())
147
+ self.lookup = {}
148
+ self.chain = []
149
+
150
+ def get(self, name):
151
+ """
152
+ Retrieve an addon by name. Addon names are equal to the .name
153
+ attribute on the instance, or the lower case class name if that
154
+ does not exist.
155
+ """
156
+ return self.lookup.get(name, None)
157
+
158
+ def register(self, addon):
159
+ """
160
+ Register an addon, call its load event, and then register all its
161
+ sub-addons. This should be used by addons that dynamically manage
162
+ addons.
163
+
164
+ If the calling addon is already running, it should follow with
165
+ running and configure events. Must be called within a current
166
+ context.
167
+ """
168
+ api_changes = {
169
+ # mitmproxy 6 -> mitmproxy 7
170
+ "clientconnect": f"The clientconnect event has been removed, use client_connected instead",
171
+ "clientdisconnect": f"The clientdisconnect event has been removed, use client_disconnected instead",
172
+ "serverconnect": "The serverconnect event has been removed, use server_connect and server_connected instead",
173
+ "serverdisconnect": f"The serverdisconnect event has been removed, use server_disconnected instead",
174
+ # mitmproxy 8 -> mitmproxy 9
175
+ "add_log": "The add_log event has been deprecated, use Python's builtin logging module instead",
176
+ }
177
+ for a in traverse([addon]):
178
+ for old, msg in api_changes.items():
179
+ if hasattr(a, old):
180
+ logger.warning(
181
+ f"{msg}. For more details, see https://docs.mitmproxy.org/dev/addons-api-changelog/."
182
+ )
183
+ name = _get_name(a)
184
+ if name in self.lookup:
185
+ raise exceptions.AddonManagerError(
186
+ "An addon called '%s' already exists." % name
187
+ )
188
+ loader = Loader(self.master)
189
+ self.invoke_addon_sync(addon, LoadHook(loader))
190
+ for a in traverse([addon]):
191
+ name = _get_name(a)
192
+ self.lookup[name] = a
193
+ for a in traverse([addon]):
194
+ self.master.commands.collect_commands(a)
195
+ self.master.options.process_deferred()
196
+ return addon
197
+
198
+ def add(self, *addons):
199
+ """
200
+ Add addons to the end of the chain, and run their load event.
201
+ If any addon has sub-addons, they are registered.
202
+ """
203
+ for i in addons:
204
+ self.chain.append(self.register(i))
205
+
206
+ def remove(self, addon):
207
+ """
208
+ Remove an addon and all its sub-addons.
209
+
210
+ If the addon is not in the chain - that is, if it's managed by a
211
+ parent addon - it's the parent's responsibility to remove it from
212
+ its own addons attribute.
213
+ """
214
+ for a in traverse([addon]):
215
+ n = _get_name(a)
216
+ if n not in self.lookup:
217
+ raise exceptions.AddonManagerError("No such addon: %s" % n)
218
+ self.chain = [i for i in self.chain if i is not a]
219
+ del self.lookup[_get_name(a)]
220
+ self.invoke_addon_sync(addon, hooks.DoneHook())
221
+
222
+ def __len__(self):
223
+ return len(self.chain)
224
+
225
+ def __str__(self):
226
+ return pprint.pformat([str(i) for i in self.chain])
227
+
228
+ def __contains__(self, item):
229
+ name = _get_name(item)
230
+ return name in self.lookup
231
+
232
+ async def handle_lifecycle(self, event: hooks.Hook):
233
+ """
234
+ Handle a lifecycle event.
235
+ """
236
+ message = event.args()[0]
237
+
238
+ await self.trigger_event(event)
239
+
240
+ if isinstance(message, flow.Flow):
241
+ await self.trigger_event(hooks.UpdateHook([message]))
242
+
243
+ def _iter_hooks(self, addon, event: hooks.Hook):
244
+ """
245
+ Enumerate all hook callables belonging to the given addon
246
+ """
247
+ assert isinstance(event, hooks.Hook)
248
+ for a in traverse([addon]):
249
+ func = getattr(a, event.name, None)
250
+ if func:
251
+ if callable(func):
252
+ yield a, func
253
+ elif isinstance(func, types.ModuleType):
254
+ # we gracefully exclude module imports with the same name as hooks.
255
+ # For example, a user may have "from mitmproxy import log" in an addon,
256
+ # which has the same name as the "log" hook. In this particular case,
257
+ # we end up in an error loop because we "log" this error.
258
+ pass
259
+ else:
260
+ raise exceptions.AddonManagerError(
261
+ f"Addon handler {event.name} ({a}) not callable"
262
+ )
263
+
264
+ async def invoke_addon(self, addon, event: hooks.Hook):
265
+ """
266
+ Asynchronously invoke an event on an addon and all its children.
267
+ """
268
+ for addon, func in self._iter_hooks(addon, event):
269
+ res = func(*event.args())
270
+ # Support both async and sync hook functions
271
+ if res is not None and inspect.isawaitable(res):
272
+ await res
273
+
274
+ def invoke_addon_sync(self, addon, event: hooks.Hook):
275
+ """
276
+ Invoke an event on an addon and all its children.
277
+ """
278
+ for addon, func in self._iter_hooks(addon, event):
279
+ if inspect.iscoroutinefunction(func):
280
+ raise exceptions.AddonManagerError(
281
+ f"Async handler {event.name} ({addon}) cannot be called from sync context"
282
+ )
283
+ func(*event.args())
284
+
285
+ async def trigger_event(self, event: hooks.Hook):
286
+ """
287
+ Asynchronously trigger an event across all addons.
288
+ """
289
+ for i in self.chain:
290
+ try:
291
+ with safecall():
292
+ await self.invoke_addon(i, event)
293
+ except exceptions.AddonHalt:
294
+ return
295
+
296
+ def trigger(self, event: hooks.Hook):
297
+ """
298
+ Trigger an event across all addons.
299
+
300
+ This API is discouraged and may be deprecated in the future.
301
+ Use `trigger_event()` instead, which provides the same functionality but supports async hooks.
302
+ """
303
+ for i in self.chain:
304
+ try:
305
+ with safecall():
306
+ self.invoke_addon_sync(i, event)
307
+ except exceptions.AddonHalt:
308
+ return
@@ -0,0 +1,67 @@
1
+ from mitmproxy.addons import anticache
2
+ from mitmproxy.addons import anticomp
3
+ from mitmproxy.addons import block
4
+ from mitmproxy.addons import blocklist
5
+ from mitmproxy.addons import browser
6
+ from mitmproxy.addons import clientplayback
7
+ from mitmproxy.addons import command_history
8
+ from mitmproxy.addons import comment
9
+ from mitmproxy.addons import core
10
+ from mitmproxy.addons import cut
11
+ from mitmproxy.addons import disable_h2c
12
+ from mitmproxy.addons import dns_resolver
13
+ from mitmproxy.addons import export
14
+ from mitmproxy.addons import maplocal
15
+ from mitmproxy.addons import mapremote
16
+ from mitmproxy.addons import modifybody
17
+ from mitmproxy.addons import modifyheaders
18
+ from mitmproxy.addons import next_layer
19
+ from mitmproxy.addons import onboarding
20
+ from mitmproxy.addons import proxyauth
21
+ from mitmproxy.addons import proxyserver
22
+ from mitmproxy.addons import save
23
+ from mitmproxy.addons import savehar
24
+ from mitmproxy.addons import script
25
+ from mitmproxy.addons import serverplayback
26
+ from mitmproxy.addons import stickyauth
27
+ from mitmproxy.addons import stickycookie
28
+ from mitmproxy.addons import strip_dns_https_records
29
+ from mitmproxy.addons import tlsconfig
30
+ from mitmproxy.addons import update_alt_svc
31
+ from mitmproxy.addons import upstream_auth
32
+
33
+
34
+ def default_addons():
35
+ return [
36
+ core.Core(),
37
+ browser.Browser(),
38
+ block.Block(),
39
+ strip_dns_https_records.StripDnsHttpsRecords(),
40
+ blocklist.BlockList(),
41
+ anticache.AntiCache(),
42
+ anticomp.AntiComp(),
43
+ clientplayback.ClientPlayback(),
44
+ command_history.CommandHistory(),
45
+ comment.Comment(),
46
+ cut.Cut(),
47
+ disable_h2c.DisableH2C(),
48
+ export.Export(),
49
+ onboarding.Onboarding(),
50
+ proxyauth.ProxyAuth(),
51
+ proxyserver.Proxyserver(),
52
+ script.ScriptLoader(),
53
+ dns_resolver.DnsResolver(),
54
+ next_layer.NextLayer(),
55
+ serverplayback.ServerPlayback(),
56
+ mapremote.MapRemote(),
57
+ maplocal.MapLocal(),
58
+ modifybody.ModifyBody(),
59
+ modifyheaders.ModifyHeaders(),
60
+ stickyauth.StickyAuth(),
61
+ stickycookie.StickyCookie(),
62
+ save.Save(),
63
+ savehar.SaveHar(),
64
+ tlsconfig.TlsConfig(),
65
+ upstream_auth.UpstreamAuth(),
66
+ update_alt_svc.UpdateAltSvc(),
67
+ ]
@@ -0,0 +1,18 @@
1
+ from mitmproxy import ctx
2
+
3
+
4
+ class AntiCache:
5
+ def load(self, loader):
6
+ loader.add_option(
7
+ "anticache",
8
+ bool,
9
+ False,
10
+ """
11
+ Strip out request headers that might cause the server to return
12
+ 304-not-modified.
13
+ """,
14
+ )
15
+
16
+ def request(self, flow):
17
+ if ctx.options.anticache:
18
+ flow.request.anticache()
@@ -0,0 +1,15 @@
1
+ from mitmproxy import ctx
2
+
3
+
4
+ class AntiComp:
5
+ def load(self, loader):
6
+ loader.add_option(
7
+ "anticomp",
8
+ bool,
9
+ False,
10
+ "Try to convince servers to send us un-compressed data.",
11
+ )
12
+
13
+ def request(self, flow):
14
+ if ctx.options.anticomp:
15
+ flow.request.anticomp()
@@ -0,0 +1,144 @@
1
+ import asyncio
2
+ import logging
3
+ import urllib.parse
4
+
5
+ import asgiref.compatibility
6
+ import asgiref.wsgi
7
+
8
+ from mitmproxy import ctx
9
+ from mitmproxy import http
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ASGIApp:
15
+ """
16
+ An addon that hosts an ASGI/WSGI HTTP app within mitmproxy, at a specified hostname and port.
17
+
18
+ Some important caveats:
19
+ - This implementation will block and wait until the entire HTTP response is completed before sending out data.
20
+ - It currently only implements the HTTP protocol (Lifespan and WebSocket are unimplemented).
21
+ """
22
+
23
+ def __init__(self, asgi_app, host: str, port: int | None):
24
+ asgi_app = asgiref.compatibility.guarantee_single_callable(asgi_app)
25
+ self.asgi_app, self.host, self.port = asgi_app, host, port
26
+
27
+ @property
28
+ def name(self) -> str:
29
+ return f"asgiapp:{self.host}:{self.port}"
30
+
31
+ def should_serve(self, flow: http.HTTPFlow) -> bool:
32
+ return bool(
33
+ flow.request.pretty_host == self.host
34
+ and (self.port is None or flow.request.port == self.port)
35
+ and flow.live
36
+ and not flow.error
37
+ and not flow.response
38
+ )
39
+
40
+ async def request(self, flow: http.HTTPFlow) -> None:
41
+ if self.should_serve(flow):
42
+ await serve(self.asgi_app, flow)
43
+
44
+
45
+ class WSGIApp(ASGIApp):
46
+ def __init__(self, wsgi_app, host: str, port: int | None):
47
+ asgi_app = asgiref.wsgi.WsgiToAsgi(wsgi_app)
48
+ super().__init__(asgi_app, host, port)
49
+
50
+
51
+ HTTP_VERSION_MAP = {
52
+ "HTTP/1.0": "1.0",
53
+ "HTTP/1.1": "1.1",
54
+ "HTTP/2.0": "2",
55
+ }
56
+
57
+
58
+ def make_scope(flow: http.HTTPFlow) -> dict:
59
+ # %3F is a quoted question mark
60
+ quoted_path = urllib.parse.quote_from_bytes(flow.request.data.path).split(
61
+ "%3F", maxsplit=1
62
+ )
63
+
64
+ # (Unicode string) – HTTP request target excluding any query string, with percent-encoded
65
+ # sequences and UTF-8 byte sequences decoded into characters.
66
+ path = quoted_path[0]
67
+
68
+ # (byte string) – URL portion after the ?, percent-encoded.
69
+ query_string: bytes
70
+ if len(quoted_path) > 1:
71
+ query_string = urllib.parse.unquote(quoted_path[1]).encode()
72
+ else:
73
+ query_string = b""
74
+
75
+ return {
76
+ "type": "http",
77
+ "asgi": {
78
+ "version": "3.0",
79
+ "spec_version": "2.1",
80
+ },
81
+ "http_version": HTTP_VERSION_MAP.get(flow.request.http_version, "1.1"),
82
+ "method": flow.request.method,
83
+ "scheme": flow.request.scheme.upper(),
84
+ "path": path,
85
+ "raw_path": flow.request.path,
86
+ "query_string": query_string,
87
+ "headers": [
88
+ (name.lower(), value) for (name, value) in flow.request.headers.fields
89
+ ],
90
+ "client": flow.client_conn.peername,
91
+ "extensions": {
92
+ "mitmproxy.master": ctx.master,
93
+ },
94
+ }
95
+
96
+
97
+ async def serve(app, flow: http.HTTPFlow):
98
+ """
99
+ Serves app on flow.
100
+ """
101
+
102
+ scope = make_scope(flow)
103
+ done = asyncio.Event()
104
+ received_body = False
105
+ sent_response = False
106
+
107
+ async def receive():
108
+ nonlocal received_body
109
+ if not received_body:
110
+ received_body = True
111
+ return {
112
+ "type": "http.request",
113
+ "body": flow.request.raw_content,
114
+ }
115
+ else: # pragma: no cover
116
+ # We really don't expect this to be called a second time, but what to do?
117
+ # We just wait until the request is done before we continue here with sending a disconnect.
118
+ await done.wait()
119
+ return {"type": "http.disconnect"}
120
+
121
+ async def send(event):
122
+ if event["type"] == "http.response.start":
123
+ flow.response = http.Response.make(
124
+ event["status"], b"", event.get("headers", [])
125
+ )
126
+ flow.response.decode()
127
+ elif event["type"] == "http.response.body":
128
+ assert flow.response
129
+ flow.response.content += event.get("body", b"")
130
+ if not event.get("more_body", False):
131
+ nonlocal sent_response
132
+ sent_response = True
133
+ else:
134
+ raise AssertionError(f"Unexpected event: {event['type']}")
135
+
136
+ try:
137
+ await app(scope, receive, send)
138
+ if not sent_response:
139
+ raise RuntimeError(f"no response sent.")
140
+ except Exception as e:
141
+ logger.exception(f"Error in asgi app: {e}")
142
+ flow.response = http.Response.make(500, b"ASGI Error.")
143
+ finally:
144
+ done.set()
@@ -0,0 +1,48 @@
1
+ import ipaddress
2
+ import logging
3
+
4
+ from mitmproxy import ctx
5
+ from mitmproxy.proxy import mode_specs
6
+
7
+
8
+ class Block:
9
+ def load(self, loader):
10
+ loader.add_option(
11
+ "block_global",
12
+ bool,
13
+ True,
14
+ """
15
+ Block connections from public IP addresses.
16
+ """,
17
+ )
18
+ loader.add_option(
19
+ "block_private",
20
+ bool,
21
+ False,
22
+ """
23
+ Block connections from local (private) IP addresses.
24
+ This option does not affect loopback addresses (connections from the local machine),
25
+ which are always permitted.
26
+ """,
27
+ )
28
+
29
+ def client_connected(self, client):
30
+ parts = client.peername[0].rsplit("%", 1)
31
+ address = ipaddress.ip_address(parts[0])
32
+ if isinstance(address, ipaddress.IPv6Address):
33
+ address = address.ipv4_mapped or address
34
+
35
+ if address.is_loopback or isinstance(client.proxy_mode, mode_specs.LocalMode):
36
+ return
37
+
38
+ if ctx.options.block_private and address.is_private:
39
+ logging.warning(
40
+ f"Client connection from {client.peername[0]} killed by block_private option."
41
+ )
42
+ client.error = "Connection killed by block_private."
43
+
44
+ if ctx.options.block_global and address.is_global:
45
+ logging.warning(
46
+ f"Client connection from {client.peername[0]} killed by block_global option."
47
+ )
48
+ client.error = "Connection killed by block_global."
@@ -0,0 +1,81 @@
1
+ from collections.abc import Sequence
2
+ from typing import NamedTuple
3
+
4
+ from mitmproxy import ctx
5
+ from mitmproxy import exceptions
6
+ from mitmproxy import flowfilter
7
+ from mitmproxy import http
8
+ from mitmproxy import version
9
+ from mitmproxy.net.http.status_codes import NO_RESPONSE
10
+
11
+
12
+ class BlockSpec(NamedTuple):
13
+ matches: flowfilter.TFilter
14
+ status_code: int
15
+
16
+
17
+ def parse_spec(option: str) -> BlockSpec:
18
+ """
19
+ Parses strings in the following format, enforces number of segments:
20
+
21
+ /flow-filter/status
22
+
23
+ """
24
+ sep, rem = option[0], option[1:]
25
+
26
+ parts = rem.split(sep, 2)
27
+ if len(parts) != 2:
28
+ raise ValueError("Invalid number of parameters (2 are expected)")
29
+ flow_patt, status = parts
30
+ try:
31
+ status_code = int(status)
32
+ except ValueError:
33
+ raise ValueError(f"Invalid HTTP status code: {status}")
34
+ flow_filter = flowfilter.parse(flow_patt)
35
+
36
+ return BlockSpec(matches=flow_filter, status_code=status_code)
37
+
38
+
39
+ class BlockList:
40
+ def __init__(self) -> None:
41
+ self.items: list[BlockSpec] = []
42
+
43
+ def load(self, loader):
44
+ loader.add_option(
45
+ "block_list",
46
+ Sequence[str],
47
+ [],
48
+ """
49
+ Block matching requests and return an empty response with the specified HTTP status.
50
+ Option syntax is "/flow-filter/status-code", where flow-filter describes
51
+ which requests this rule should be applied to and status-code is the HTTP status code to return for
52
+ blocked requests. The separator ("/" in the example) can be any character.
53
+ Setting a non-standard status code of 444 will close the connection without sending a response.
54
+ """,
55
+ )
56
+
57
+ def configure(self, updated):
58
+ if "block_list" in updated:
59
+ self.items = []
60
+ for option in ctx.options.block_list:
61
+ try:
62
+ spec = parse_spec(option)
63
+ except ValueError as e:
64
+ raise exceptions.OptionsError(
65
+ f"Cannot parse block_list option {option}: {e}"
66
+ ) from e
67
+ self.items.append(spec)
68
+
69
+ def request(self, flow: http.HTTPFlow) -> None:
70
+ if flow.response or flow.error or not flow.live:
71
+ return
72
+
73
+ for spec in self.items:
74
+ if spec.matches(flow):
75
+ flow.metadata["blocklisted"] = True
76
+ if spec.status_code == NO_RESPONSE:
77
+ flow.kill()
78
+ else:
79
+ flow.response = http.Response.make(
80
+ spec.status_code, headers={"Server": version.MITMPROXY}
81
+ )