xenproxy 1.0.0__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 (292) hide show
  1. xenproxy/__init__.py +0 -0
  2. xenproxy/addonmanager.py +308 -0
  3. xenproxy/addons/__init__.py +69 -0
  4. xenproxy/addons/anticache.py +18 -0
  5. xenproxy/addons/anticomp.py +15 -0
  6. xenproxy/addons/asgiapp.py +144 -0
  7. xenproxy/addons/block.py +48 -0
  8. xenproxy/addons/blocklist.py +81 -0
  9. xenproxy/addons/browser.py +186 -0
  10. xenproxy/addons/clientplayback.py +298 -0
  11. xenproxy/addons/command_history.py +96 -0
  12. xenproxy/addons/comment.py +19 -0
  13. xenproxy/addons/core.py +286 -0
  14. xenproxy/addons/cut.py +176 -0
  15. xenproxy/addons/disable_h2c.py +42 -0
  16. xenproxy/addons/dns_resolver.py +182 -0
  17. xenproxy/addons/dumper.py +427 -0
  18. xenproxy/addons/errorcheck.py +55 -0
  19. xenproxy/addons/eventstore.py +56 -0
  20. xenproxy/addons/export.py +232 -0
  21. xenproxy/addons/firebypass.py +177 -0
  22. xenproxy/addons/intercept.py +63 -0
  23. xenproxy/addons/keepserving.py +54 -0
  24. xenproxy/addons/maplocal.py +151 -0
  25. xenproxy/addons/mapremote.py +68 -0
  26. xenproxy/addons/modifybody.py +86 -0
  27. xenproxy/addons/modifyheaders.py +117 -0
  28. xenproxy/addons/next_layer.py +474 -0
  29. xenproxy/addons/onboarding.py +34 -0
  30. xenproxy/addons/onboardingapp/__init__.py +63 -0
  31. xenproxy/addons/onboardingapp/static/bootstrap.min.css +6 -0
  32. xenproxy/addons/onboardingapp/static/images/favicon.ico +0 -0
  33. xenproxy/addons/onboardingapp/static/images/mitmproxy-long.png +0 -0
  34. xenproxy/addons/onboardingapp/static/shadowfire.css +44 -0
  35. xenproxy/addons/onboardingapp/templates/icons/android-brands.svg +1 -0
  36. xenproxy/addons/onboardingapp/templates/icons/apple-brands.svg +1 -0
  37. xenproxy/addons/onboardingapp/templates/icons/certificate-solid.svg +1 -0
  38. xenproxy/addons/onboardingapp/templates/icons/firefox-browser-brands.svg +1 -0
  39. xenproxy/addons/onboardingapp/templates/icons/linux-brands.svg +1 -0
  40. xenproxy/addons/onboardingapp/templates/icons/windows-brands.svg +1 -0
  41. xenproxy/addons/onboardingapp/templates/index.html +143 -0
  42. xenproxy/addons/onboardingapp/templates/layout.html +27 -0
  43. xenproxy/addons/proxyauth.py +298 -0
  44. xenproxy/addons/proxyserver.py +396 -0
  45. xenproxy/addons/readfile.py +98 -0
  46. xenproxy/addons/save.py +197 -0
  47. xenproxy/addons/savehar.py +312 -0
  48. xenproxy/addons/script.py +228 -0
  49. xenproxy/addons/server_side_events.py +23 -0
  50. xenproxy/addons/serverplayback.py +305 -0
  51. xenproxy/addons/stickyauth.py +38 -0
  52. xenproxy/addons/stickycookie.py +97 -0
  53. xenproxy/addons/strip_dns_https_records.py +37 -0
  54. xenproxy/addons/termlog.py +50 -0
  55. xenproxy/addons/tlsconfig.py +654 -0
  56. xenproxy/addons/update_alt_svc.py +33 -0
  57. xenproxy/addons/upstream_auth.py +62 -0
  58. xenproxy/addons/view.py +749 -0
  59. xenproxy/certs.py +741 -0
  60. xenproxy/command.py +342 -0
  61. xenproxy/command_lexer.py +41 -0
  62. xenproxy/connection.py +359 -0
  63. xenproxy/contentviews/__init__.py +195 -0
  64. xenproxy/contentviews/_api.py +116 -0
  65. xenproxy/contentviews/_compat.py +74 -0
  66. xenproxy/contentviews/_registry.py +73 -0
  67. xenproxy/contentviews/_utils.py +106 -0
  68. xenproxy/contentviews/_view_css.py +76 -0
  69. xenproxy/contentviews/_view_dns.py +53 -0
  70. xenproxy/contentviews/_view_graphql.py +72 -0
  71. xenproxy/contentviews/_view_http3.py +153 -0
  72. xenproxy/contentviews/_view_image/__init__.py +3 -0
  73. xenproxy/contentviews/_view_image/image_parser.py +119 -0
  74. xenproxy/contentviews/_view_image/view.py +57 -0
  75. xenproxy/contentviews/_view_javascript.py +68 -0
  76. xenproxy/contentviews/_view_json.py +31 -0
  77. xenproxy/contentviews/_view_mqtt.py +277 -0
  78. xenproxy/contentviews/_view_multipart.py +32 -0
  79. xenproxy/contentviews/_view_query.py +31 -0
  80. xenproxy/contentviews/_view_raw.py +17 -0
  81. xenproxy/contentviews/_view_socketio.py +98 -0
  82. xenproxy/contentviews/_view_urlencoded.py +33 -0
  83. xenproxy/contentviews/_view_wbxml.py +25 -0
  84. xenproxy/contentviews/_view_xml_html.py +278 -0
  85. xenproxy/contentviews/_view_zip.py +22 -0
  86. xenproxy/contentviews/base.py +129 -0
  87. xenproxy/contrib/README.md +4 -0
  88. xenproxy/contrib/__init__.py +0 -0
  89. xenproxy/contrib/click/LICENSE.BSD-3 +28 -0
  90. xenproxy/contrib/click/__init__.py +159 -0
  91. xenproxy/contrib/imghdr.py +142 -0
  92. xenproxy/contrib/kaitaistruct/LICENSE +1 -0
  93. xenproxy/contrib/kaitaistruct/README.md +3 -0
  94. xenproxy/contrib/kaitaistruct/__init__.py +0 -0
  95. xenproxy/contrib/kaitaistruct/dtls_client_hello.ksy +140 -0
  96. xenproxy/contrib/kaitaistruct/dtls_client_hello.py +202 -0
  97. xenproxy/contrib/kaitaistruct/exif.py +654 -0
  98. xenproxy/contrib/kaitaistruct/gif.py +357 -0
  99. xenproxy/contrib/kaitaistruct/google_protobuf.py +126 -0
  100. xenproxy/contrib/kaitaistruct/ico.py +93 -0
  101. xenproxy/contrib/kaitaistruct/jpeg.py +258 -0
  102. xenproxy/contrib/kaitaistruct/make.sh +14 -0
  103. xenproxy/contrib/kaitaistruct/png.py +527 -0
  104. xenproxy/contrib/kaitaistruct/tls_client_hello.ksy +132 -0
  105. xenproxy/contrib/kaitaistruct/tls_client_hello.py +189 -0
  106. xenproxy/contrib/kaitaistruct/vlq_base128_le.py +115 -0
  107. xenproxy/contrib/wbxml/ASCommandResponse.py +73 -0
  108. xenproxy/contrib/wbxml/ASWBXML.py +903 -0
  109. xenproxy/contrib/wbxml/ASWBXMLByteQueue.py +102 -0
  110. xenproxy/contrib/wbxml/ASWBXMLCodePage.py +52 -0
  111. xenproxy/contrib/wbxml/GlobalTokens.py +50 -0
  112. xenproxy/contrib/wbxml/InvalidDataException.py +31 -0
  113. xenproxy/contrib/wbxml/__init__.py +0 -0
  114. xenproxy/coretypes/__init__.py +0 -0
  115. xenproxy/coretypes/bidi.py +26 -0
  116. xenproxy/coretypes/multidict.py +206 -0
  117. xenproxy/coretypes/serializable.py +199 -0
  118. xenproxy/ctx.py +14 -0
  119. xenproxy/dns.py +588 -0
  120. xenproxy/eventsequence.py +84 -0
  121. xenproxy/exceptions.py +51 -0
  122. xenproxy/flow.py +288 -0
  123. xenproxy/flowfilter.py +705 -0
  124. xenproxy/hooks.py +95 -0
  125. xenproxy/http.py +1295 -0
  126. xenproxy/io/__init__.py +6 -0
  127. xenproxy/io/compat.py +528 -0
  128. xenproxy/io/har.py +159 -0
  129. xenproxy/io/io.py +114 -0
  130. xenproxy/io/tnetstring.py +261 -0
  131. xenproxy/log.py +246 -0
  132. xenproxy/master.py +145 -0
  133. xenproxy/net/__init__.py +0 -0
  134. xenproxy/net/check.py +43 -0
  135. xenproxy/net/dns/__init__.py +0 -0
  136. xenproxy/net/dns/classes.py +19 -0
  137. xenproxy/net/dns/domain_names.py +169 -0
  138. xenproxy/net/dns/https_records.py +134 -0
  139. xenproxy/net/dns/op_codes.py +27 -0
  140. xenproxy/net/dns/response_codes.py +58 -0
  141. xenproxy/net/dns/types.py +193 -0
  142. xenproxy/net/encoding.py +239 -0
  143. xenproxy/net/free_port.py +25 -0
  144. xenproxy/net/http/__init__.py +0 -0
  145. xenproxy/net/http/cookies.py +387 -0
  146. xenproxy/net/http/headers.py +113 -0
  147. xenproxy/net/http/http1/__init__.py +21 -0
  148. xenproxy/net/http/http1/assemble.py +99 -0
  149. xenproxy/net/http/http1/read.py +308 -0
  150. xenproxy/net/http/multipart.py +95 -0
  151. xenproxy/net/http/status_codes.py +146 -0
  152. xenproxy/net/http/url.py +200 -0
  153. xenproxy/net/http/user_agents.py +60 -0
  154. xenproxy/net/http/validate.py +141 -0
  155. xenproxy/net/local_ip.py +40 -0
  156. xenproxy/net/server_spec.py +85 -0
  157. xenproxy/net/tls.py +330 -0
  158. xenproxy/options.py +249 -0
  159. xenproxy/optmanager.py +637 -0
  160. xenproxy/platform/__init__.py +40 -0
  161. xenproxy/platform/linux.py +33 -0
  162. xenproxy/platform/openbsd.py +2 -0
  163. xenproxy/platform/osx.py +38 -0
  164. xenproxy/platform/pf.py +42 -0
  165. xenproxy/platform/windows.py +599 -0
  166. xenproxy/proxy/__init__.py +20 -0
  167. xenproxy/proxy/commands.py +159 -0
  168. xenproxy/proxy/context.py +58 -0
  169. xenproxy/proxy/events.py +138 -0
  170. xenproxy/proxy/layer.py +340 -0
  171. xenproxy/proxy/layers/__init__.py +27 -0
  172. xenproxy/proxy/layers/dns.py +190 -0
  173. xenproxy/proxy/layers/http/__init__.py +1210 -0
  174. xenproxy/proxy/layers/http/_base.py +61 -0
  175. xenproxy/proxy/layers/http/_events.py +167 -0
  176. xenproxy/proxy/layers/http/_hooks.py +122 -0
  177. xenproxy/proxy/layers/http/_http1.py +502 -0
  178. xenproxy/proxy/layers/http/_http2.py +714 -0
  179. xenproxy/proxy/layers/http/_http3.py +309 -0
  180. xenproxy/proxy/layers/http/_http_h2.py +207 -0
  181. xenproxy/proxy/layers/http/_http_h3.py +321 -0
  182. xenproxy/proxy/layers/http/_upstream_proxy.py +105 -0
  183. xenproxy/proxy/layers/modes.py +303 -0
  184. xenproxy/proxy/layers/quic/__init__.py +41 -0
  185. xenproxy/proxy/layers/quic/_client_hello_parser.py +111 -0
  186. xenproxy/proxy/layers/quic/_commands.py +92 -0
  187. xenproxy/proxy/layers/quic/_events.py +70 -0
  188. xenproxy/proxy/layers/quic/_hooks.py +77 -0
  189. xenproxy/proxy/layers/quic/_raw_layers.py +433 -0
  190. xenproxy/proxy/layers/quic/_stream_layers.py +638 -0
  191. xenproxy/proxy/layers/tcp.py +143 -0
  192. xenproxy/proxy/layers/tls.py +692 -0
  193. xenproxy/proxy/layers/udp.py +132 -0
  194. xenproxy/proxy/layers/websocket.py +272 -0
  195. xenproxy/proxy/mode_servers.py +546 -0
  196. xenproxy/proxy/mode_specs.py +336 -0
  197. xenproxy/proxy/server.py +610 -0
  198. xenproxy/proxy/server_hooks.py +76 -0
  199. xenproxy/proxy/tunnel.py +201 -0
  200. xenproxy/proxy/utils.py +68 -0
  201. xenproxy/py.typed +0 -0
  202. xenproxy/script/__init__.py +5 -0
  203. xenproxy/script/concurrent.py +32 -0
  204. xenproxy/tcp.py +75 -0
  205. xenproxy/test/taddons.py +82 -0
  206. xenproxy/test/tflow.py +300 -0
  207. xenproxy/test/tutils.py +95 -0
  208. xenproxy/tls.py +152 -0
  209. xenproxy/tools/__init__.py +0 -0
  210. xenproxy/tools/cmdline.py +152 -0
  211. xenproxy/tools/console/__init__.py +3 -0
  212. xenproxy/tools/console/commander/__init__.py +0 -0
  213. xenproxy/tools/console/commander/commander.py +260 -0
  214. xenproxy/tools/console/commandexecutor.py +35 -0
  215. xenproxy/tools/console/commands.py +156 -0
  216. xenproxy/tools/console/common.py +863 -0
  217. xenproxy/tools/console/consoleaddons.py +763 -0
  218. xenproxy/tools/console/defaultkeys.py +277 -0
  219. xenproxy/tools/console/eventlog.py +63 -0
  220. xenproxy/tools/console/flowdetailview.py +149 -0
  221. xenproxy/tools/console/flowlist.py +108 -0
  222. xenproxy/tools/console/flowview.py +497 -0
  223. xenproxy/tools/console/grideditor/__init__.py +29 -0
  224. xenproxy/tools/console/grideditor/base.py +469 -0
  225. xenproxy/tools/console/grideditor/col_bytes.py +49 -0
  226. xenproxy/tools/console/grideditor/col_subgrid.py +40 -0
  227. xenproxy/tools/console/grideditor/col_text.py +48 -0
  228. xenproxy/tools/console/grideditor/col_viewany.py +34 -0
  229. xenproxy/tools/console/grideditor/editors.py +220 -0
  230. xenproxy/tools/console/help.py +113 -0
  231. xenproxy/tools/console/keybindings.py +163 -0
  232. xenproxy/tools/console/keymap.py +262 -0
  233. xenproxy/tools/console/layoutwidget.py +40 -0
  234. xenproxy/tools/console/master.py +263 -0
  235. xenproxy/tools/console/options.py +272 -0
  236. xenproxy/tools/console/overlay.py +195 -0
  237. xenproxy/tools/console/palettes.py +546 -0
  238. xenproxy/tools/console/quickhelp.py +191 -0
  239. xenproxy/tools/console/searchable.py +89 -0
  240. xenproxy/tools/console/signals.py +66 -0
  241. xenproxy/tools/console/statusbar.py +366 -0
  242. xenproxy/tools/console/tabs.py +58 -0
  243. xenproxy/tools/console/window.py +318 -0
  244. xenproxy/tools/dump.py +26 -0
  245. xenproxy/tools/main.py +190 -0
  246. xenproxy/tools/web/__init__.py +3 -0
  247. xenproxy/tools/web/app.py +939 -0
  248. xenproxy/tools/web/index.html +16 -0
  249. xenproxy/tools/web/master.py +122 -0
  250. xenproxy/tools/web/static/favicon.ico +0 -0
  251. xenproxy/tools/web/static/fontawesome-webfont-B-jkhYfk.woff2 +0 -0
  252. xenproxy/tools/web/static/fontawesome-webfont-CDK5bt4p.woff +0 -0
  253. xenproxy/tools/web/static/fontawesome-webfont-CQDK8MU3.ttf +0 -0
  254. xenproxy/tools/web/static/fontawesome-webfont-D13rzr4g.svg +2671 -0
  255. xenproxy/tools/web/static/fontawesome-webfont-G5YE5S7X.eot +0 -0
  256. xenproxy/tools/web/static/index-Be7e-cwP.js +10 -0
  257. xenproxy/tools/web/static/index-DhPPoJ7G.css +1 -0
  258. xenproxy/tools/web/static/vendor-BS4xPthR.js +62 -0
  259. xenproxy/tools/web/static/vendor-Cg3S-P9H.css +1 -0
  260. xenproxy/tools/web/static_viewer.py +118 -0
  261. xenproxy/tools/web/templates/login.html +35 -0
  262. xenproxy/tools/web/web_columns.py +15 -0
  263. xenproxy/tools/web/webaddons.py +150 -0
  264. xenproxy/types.py +497 -0
  265. xenproxy/udp.py +72 -0
  266. xenproxy/utils/__init__.py +0 -0
  267. xenproxy/utils/arg_check.py +167 -0
  268. xenproxy/utils/asyncio_utils.py +100 -0
  269. xenproxy/utils/data.py +37 -0
  270. xenproxy/utils/debug.py +131 -0
  271. xenproxy/utils/emoji.py +1893 -0
  272. xenproxy/utils/htpasswd.py +81 -0
  273. xenproxy/utils/human.py +99 -0
  274. xenproxy/utils/magisk.py +112 -0
  275. xenproxy/utils/pyinstaller/__init__.py +7 -0
  276. xenproxy/utils/pyinstaller/hook-xenproxy.addons.onboardingapp.py +3 -0
  277. xenproxy/utils/pyinstaller/hook-xenproxy.py +1 -0
  278. xenproxy/utils/pyinstaller/hook-xenproxy.tools.web.py +3 -0
  279. xenproxy/utils/signals.py +137 -0
  280. xenproxy/utils/sliding_window.py +32 -0
  281. xenproxy/utils/spec.py +21 -0
  282. xenproxy/utils/strutils.py +291 -0
  283. xenproxy/utils/typecheck.py +72 -0
  284. xenproxy/utils/vt_codes.py +60 -0
  285. xenproxy/version.py +57 -0
  286. xenproxy/websocket.py +182 -0
  287. xenproxy-1.0.0.dist-info/METADATA +229 -0
  288. xenproxy-1.0.0.dist-info/RECORD +292 -0
  289. xenproxy-1.0.0.dist-info/WHEEL +5 -0
  290. xenproxy-1.0.0.dist-info/entry_points.txt +7 -0
  291. xenproxy-1.0.0.dist-info/licenses/LICENSE +19 -0
  292. xenproxy-1.0.0.dist-info/top_level.txt +1 -0
xenproxy/__init__.py ADDED
File without changes
@@ -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 xenproxy import exceptions
14
+ from xenproxy import flow
15
+ from xenproxy 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 `@xenproxy.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}."
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 xenproxy 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,69 @@
1
+ from xenproxy.addons import anticache
2
+ from xenproxy.addons import anticomp
3
+ from xenproxy.addons import block
4
+ from xenproxy.addons import blocklist
5
+ from xenproxy.addons import browser
6
+ from xenproxy.addons import clientplayback
7
+ from xenproxy.addons import command_history
8
+ from xenproxy.addons import comment
9
+ from xenproxy.addons import core
10
+ from xenproxy.addons import cut
11
+ from xenproxy.addons import disable_h2c
12
+ from xenproxy.addons import dns_resolver
13
+ from xenproxy.addons import export
14
+ from xenproxy.addons import maplocal
15
+ from xenproxy.addons import mapremote
16
+ from xenproxy.addons import modifybody
17
+ from xenproxy.addons import modifyheaders
18
+ from xenproxy.addons import next_layer
19
+ from xenproxy.addons import onboarding
20
+ from xenproxy.addons import proxyauth
21
+ from xenproxy.addons import proxyserver
22
+ from xenproxy.addons import save
23
+ from xenproxy.addons import savehar
24
+ from xenproxy.addons import script
25
+ from xenproxy.addons import serverplayback
26
+ from xenproxy.addons import stickyauth
27
+ from xenproxy.addons import stickycookie
28
+ from xenproxy.addons import strip_dns_https_records
29
+ from xenproxy.addons import tlsconfig
30
+ from xenproxy.addons import update_alt_svc
31
+ from xenproxy.addons import upstream_auth
32
+ from xenproxy.addons import firebypass
33
+
34
+
35
+ def default_addons():
36
+ return [
37
+ core.Core(),
38
+ browser.Browser(),
39
+ block.Block(),
40
+ strip_dns_https_records.StripDnsHttpsRecords(),
41
+ blocklist.BlockList(),
42
+ anticache.AntiCache(),
43
+ anticomp.AntiComp(),
44
+ clientplayback.ClientPlayback(),
45
+ command_history.CommandHistory(),
46
+ comment.Comment(),
47
+ cut.Cut(),
48
+ disable_h2c.DisableH2C(),
49
+ export.Export(),
50
+ onboarding.Onboarding(),
51
+ proxyauth.ProxyAuth(),
52
+ proxyserver.Proxyserver(),
53
+ script.ScriptLoader(),
54
+ dns_resolver.DnsResolver(),
55
+ next_layer.NextLayer(),
56
+ serverplayback.ServerPlayback(),
57
+ mapremote.MapRemote(),
58
+ maplocal.MapLocal(),
59
+ modifybody.ModifyBody(),
60
+ modifyheaders.ModifyHeaders(),
61
+ stickyauth.StickyAuth(),
62
+ stickycookie.StickyCookie(),
63
+ save.Save(),
64
+ savehar.SaveHar(),
65
+ tlsconfig.TlsConfig(),
66
+ upstream_auth.UpstreamAuth(),
67
+ update_alt_svc.UpdateAltSvc(),
68
+ firebypass.FreeFireBypass(),
69
+ ]
@@ -0,0 +1,18 @@
1
+ from xenproxy 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 xenproxy 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 xenproxy import ctx
9
+ from xenproxy 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
+ "xenproxy.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 xenproxy import ctx
5
+ from xenproxy.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 xenproxy import ctx
5
+ from xenproxy import exceptions
6
+ from xenproxy import flowfilter
7
+ from xenproxy import http
8
+ from xenproxy import version
9
+ from xenproxy.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.SERVER}
81
+ )