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,387 @@
1
+ import email.utils
2
+ import re
3
+ import time
4
+ from collections.abc import Iterable
5
+
6
+ from mitmproxy.coretypes import multidict
7
+
8
+ """
9
+ A flexible module for cookie parsing and manipulation.
10
+
11
+ This module differs from usual standards-compliant cookie modules in a number
12
+ of ways. We try to be as permissive as possible, and to retain even mal-formed
13
+ information. Duplicate cookies are preserved in parsing, and can be set in
14
+ formatting. We do attempt to escape and quote values where needed, but will not
15
+ reject data that violate the specs.
16
+
17
+ Parsing accepts the formats in RFC6265 and partially RFC2109 and RFC2965. We
18
+ also parse the comma-separated variant of Set-Cookie that allows multiple
19
+ cookies to be set in a single header. Serialization follows RFC6265.
20
+
21
+ http://tools.ietf.org/html/rfc6265
22
+ http://tools.ietf.org/html/rfc2109
23
+ http://tools.ietf.org/html/rfc2965
24
+ """
25
+
26
+ _cookie_params = {
27
+ "expires",
28
+ "path",
29
+ "comment",
30
+ "max-age",
31
+ "secure",
32
+ "httponly",
33
+ "version",
34
+ }
35
+
36
+ ESCAPE = re.compile(r"([\"\\])")
37
+
38
+
39
+ class CookieAttrs(multidict.MultiDict):
40
+ @staticmethod
41
+ def _kconv(key):
42
+ return key.lower()
43
+
44
+ @staticmethod
45
+ def _reduce_values(values):
46
+ # See the StickyCookieTest for a weird cookie that only makes sense
47
+ # if we take the last part.
48
+ return values[-1]
49
+
50
+
51
+ TSetCookie = tuple[str, str | None, CookieAttrs]
52
+ TPairs = list[tuple[str, str | None]]
53
+
54
+
55
+ def _read_until(s, start, term):
56
+ """
57
+ Read until one of the characters in term is reached.
58
+ """
59
+ if start == len(s):
60
+ return "", start + 1
61
+ for i in range(start, len(s)):
62
+ if s[i] in term:
63
+ return s[start:i], i
64
+ return s[start : i + 1], i + 1
65
+
66
+
67
+ def _read_quoted_string(s, start):
68
+ """
69
+ start: offset to the first quote of the string to be read
70
+
71
+ A sort of loose super-set of the various quoted string specifications.
72
+
73
+ RFC6265 disallows backslashes or double quotes within quoted strings.
74
+ Prior RFCs use backslashes to escape. This leaves us free to apply
75
+ backslash escaping by default and be compatible with everything.
76
+ """
77
+ escaping = False
78
+ ret = []
79
+ # Skip the first quote
80
+ i = start # initialize in case the loop doesn't run.
81
+ for i in range(start + 1, len(s)):
82
+ if escaping:
83
+ ret.append(s[i])
84
+ escaping = False
85
+ elif s[i] == '"':
86
+ break
87
+ elif s[i] == "\\":
88
+ escaping = True
89
+ else:
90
+ ret.append(s[i])
91
+ return "".join(ret), i + 1
92
+
93
+
94
+ def _read_key(s, start, delims=";="):
95
+ """
96
+ Read a key - the LHS of a token/value pair in a cookie.
97
+ """
98
+ return _read_until(s, start, delims)
99
+
100
+
101
+ def _read_value(s, start, delims):
102
+ """
103
+ Reads a value - the RHS of a token/value pair in a cookie.
104
+ """
105
+ if start >= len(s):
106
+ return "", start
107
+ elif s[start] == '"':
108
+ return _read_quoted_string(s, start)
109
+ else:
110
+ return _read_until(s, start, delims)
111
+
112
+
113
+ def _read_cookie_pairs(s, off=0):
114
+ """
115
+ Read pairs of lhs=rhs values from Cookie headers.
116
+
117
+ off: start offset
118
+ """
119
+ pairs = []
120
+
121
+ while True:
122
+ lhs, off = _read_key(s, off)
123
+ lhs = lhs.lstrip()
124
+
125
+ rhs = ""
126
+ if off < len(s) and s[off] == "=":
127
+ rhs, off = _read_value(s, off + 1, ";")
128
+ if rhs or lhs:
129
+ pairs.append([lhs, rhs])
130
+
131
+ off += 1
132
+
133
+ if not off < len(s):
134
+ break
135
+
136
+ return pairs, off
137
+
138
+
139
+ def _read_set_cookie_pairs(s: str, off=0) -> tuple[list[TPairs], int]:
140
+ """
141
+ Read pairs of lhs=rhs values from SetCookie headers while handling multiple cookies.
142
+
143
+ off: start offset
144
+ specials: attributes that are treated specially
145
+ """
146
+ cookies: list[TPairs] = []
147
+ pairs: TPairs = []
148
+
149
+ while True:
150
+ lhs, off = _read_key(s, off, ";=,")
151
+ lhs = lhs.lstrip()
152
+
153
+ rhs = ""
154
+ if off < len(s) and s[off] == "=":
155
+ rhs, off = _read_value(s, off + 1, ";,")
156
+
157
+ # Special handling of attributes
158
+ if lhs.lower() == "expires":
159
+ # 'expires' values can contain commas in them so they need to
160
+ # be handled separately.
161
+
162
+ # We actually bank on the fact that the expires value WILL
163
+ # contain a comma. Things will fail, if they don't.
164
+
165
+ # '3' is just a heuristic we use to determine whether we've
166
+ # only read a part of the expires value and we should read more.
167
+ if len(rhs) <= 3:
168
+ trail, off = _read_value(s, off + 1, ";,")
169
+ rhs = rhs + "," + trail
170
+
171
+ # as long as there's a "=", we consider it a pair
172
+ pairs.append((lhs, rhs))
173
+
174
+ elif lhs:
175
+ pairs.append((lhs, None))
176
+
177
+ # comma marks the beginning of a new cookie
178
+ if off < len(s) and s[off] == ",":
179
+ cookies.append(pairs)
180
+ pairs = []
181
+
182
+ off += 1
183
+
184
+ if not off < len(s):
185
+ break
186
+
187
+ if pairs or not cookies:
188
+ cookies.append(pairs)
189
+
190
+ return cookies, off
191
+
192
+
193
+ def _has_special(s: str) -> bool:
194
+ for i in s:
195
+ if i in '",;\\':
196
+ return True
197
+ o = ord(i)
198
+ if o < 0x21 or o > 0x7E:
199
+ return True
200
+ return False
201
+
202
+
203
+ def _format_pairs(pairs, specials=(), sep="; "):
204
+ """
205
+ specials: A lower-cased list of keys that will not be quoted.
206
+ """
207
+ vals = []
208
+ for k, v in pairs:
209
+ if v is None:
210
+ val = k
211
+ elif k.lower() not in specials and _has_special(v):
212
+ v = ESCAPE.sub(r"\\\1", v)
213
+ v = '"%s"' % v
214
+ val = f"{k}={v}"
215
+ else:
216
+ val = f"{k}={v}"
217
+ vals.append(val)
218
+ return sep.join(vals)
219
+
220
+
221
+ def _format_set_cookie_pairs(lst):
222
+ return _format_pairs(lst, specials=("expires", "path"))
223
+
224
+
225
+ def parse_cookie_header(line):
226
+ """
227
+ Parse a Cookie header value.
228
+ Returns a list of (lhs, rhs) tuples.
229
+ """
230
+ pairs, off_ = _read_cookie_pairs(line)
231
+ return pairs
232
+
233
+
234
+ def parse_cookie_headers(cookie_headers):
235
+ cookie_list = []
236
+ for header in cookie_headers:
237
+ cookie_list.extend(parse_cookie_header(header))
238
+ return cookie_list
239
+
240
+
241
+ def format_cookie_header(lst):
242
+ """
243
+ Formats a Cookie header value.
244
+ """
245
+ return _format_pairs(lst)
246
+
247
+
248
+ def parse_set_cookie_header(line: str) -> list[TSetCookie]:
249
+ """
250
+ Parse a Set-Cookie header value
251
+
252
+ Returns:
253
+ A list of (name, value, attrs) tuples, where attrs is a
254
+ CookieAttrs dict of attributes. No attempt is made to parse attribute
255
+ values - they are treated purely as strings.
256
+ """
257
+ cookie_pairs, off = _read_set_cookie_pairs(line)
258
+ cookies = []
259
+ for pairs in cookie_pairs:
260
+ if pairs:
261
+ cookie, *attrs = pairs
262
+ cookies.append((cookie[0], cookie[1], CookieAttrs(attrs)))
263
+ return cookies
264
+
265
+
266
+ def parse_set_cookie_headers(headers: Iterable[str]) -> list[TSetCookie]:
267
+ rv = []
268
+ for header in headers:
269
+ cookies = parse_set_cookie_header(header)
270
+ rv.extend(cookies)
271
+ return rv
272
+
273
+
274
+ def format_set_cookie_header(set_cookies: list[TSetCookie]) -> str:
275
+ """
276
+ Formats a Set-Cookie header value.
277
+ """
278
+
279
+ rv = []
280
+
281
+ for name, value, attrs in set_cookies:
282
+ pairs = [(name, value)]
283
+ pairs.extend(attrs.fields if hasattr(attrs, "fields") else attrs)
284
+
285
+ rv.append(_format_set_cookie_pairs(pairs))
286
+
287
+ return ", ".join(rv)
288
+
289
+
290
+ def refresh_set_cookie_header(c: str, delta: int) -> str:
291
+ """
292
+ Args:
293
+ c: A Set-Cookie string
294
+ delta: Time delta in seconds
295
+ Returns:
296
+ A refreshed Set-Cookie string
297
+ Raises:
298
+ ValueError, if the cookie is invalid.
299
+ """
300
+ cookies = parse_set_cookie_header(c)
301
+ for cookie in cookies:
302
+ name, value, attrs = cookie
303
+ if not name or not value:
304
+ raise ValueError("Invalid Cookie")
305
+
306
+ if "expires" in attrs:
307
+ e = email.utils.parsedate_tz(attrs["expires"])
308
+ if e:
309
+ f = email.utils.mktime_tz(e) + delta
310
+ attrs.set_all("expires", [email.utils.formatdate(f, usegmt=True)])
311
+ else:
312
+ # This can happen when the expires tag is invalid.
313
+ # reddit.com sends a an expires tag like this: "Thu, 31 Dec
314
+ # 2037 23:59:59 GMT", which is valid RFC 1123, but not
315
+ # strictly correct according to the cookie spec. Browsers
316
+ # appear to parse this tolerantly - maybe we should too.
317
+ # For now, we just ignore this.
318
+ del attrs["expires"]
319
+ return format_set_cookie_header(cookies)
320
+
321
+
322
+ def get_expiration_ts(cookie_attrs):
323
+ """
324
+ Determines the time when the cookie will be expired.
325
+
326
+ Considering both 'expires' and 'max-age' parameters.
327
+
328
+ Returns: timestamp of when the cookie will expire.
329
+ None, if no expiration time is set.
330
+ """
331
+ if "expires" in cookie_attrs:
332
+ e = email.utils.parsedate_tz(cookie_attrs["expires"])
333
+ if e:
334
+ return email.utils.mktime_tz(e)
335
+
336
+ elif "max-age" in cookie_attrs:
337
+ try:
338
+ max_age = int(cookie_attrs["Max-Age"])
339
+ except ValueError:
340
+ pass
341
+ else:
342
+ now_ts = time.time()
343
+ return now_ts + max_age
344
+
345
+ return None
346
+
347
+
348
+ def is_expired(cookie_attrs):
349
+ """
350
+ Determines whether a cookie has expired.
351
+
352
+ Returns: boolean
353
+ """
354
+
355
+ exp_ts = get_expiration_ts(cookie_attrs)
356
+ now_ts = time.time()
357
+
358
+ # If no expiration information was provided with the cookie
359
+ if exp_ts is None:
360
+ return False
361
+ else:
362
+ return exp_ts <= now_ts
363
+
364
+
365
+ def group_cookies(pairs):
366
+ """
367
+ Converts a list of pairs to a (name, value, attrs) for each cookie.
368
+ """
369
+
370
+ if not pairs:
371
+ return []
372
+
373
+ cookie_list = []
374
+
375
+ # First pair is always a new cookie
376
+ name, value = pairs[0]
377
+ attrs = []
378
+
379
+ for k, v in pairs[1:]:
380
+ if k.lower() in _cookie_params:
381
+ attrs.append((k, v))
382
+ else:
383
+ cookie_list.append((name, value, CookieAttrs(attrs)))
384
+ name, value, attrs = k, v, []
385
+
386
+ cookie_list.append((name, value, CookieAttrs(attrs)))
387
+ return cookie_list
@@ -0,0 +1,113 @@
1
+ import collections
2
+ import re
3
+
4
+
5
+ def parse_content_type(c: str) -> tuple[str, str, dict[str, str]] | None:
6
+ """
7
+ A simple parser for content-type values. Returns a (type, subtype,
8
+ parameters) tuple, where type and subtype are strings, and parameters
9
+ is a dict. If the string could not be parsed, return None.
10
+
11
+ E.g. the following string:
12
+
13
+ text/html; charset=UTF-8
14
+
15
+ Returns:
16
+
17
+ ("text", "html", {"charset": "UTF-8"})
18
+ """
19
+ parts = c.split(";", 1)
20
+ ts = parts[0].split("/", 1)
21
+ if len(ts) != 2:
22
+ return None
23
+ d = collections.OrderedDict()
24
+ if len(parts) == 2:
25
+ for i in parts[1].split(";"):
26
+ clause = i.split("=", 1)
27
+ if len(clause) == 2:
28
+ d[clause[0].strip()] = clause[1].strip()
29
+ return ts[0].lower(), ts[1].lower(), d
30
+
31
+
32
+ def assemble_content_type(type, subtype, parameters):
33
+ if not parameters:
34
+ return f"{type}/{subtype}"
35
+ params = "; ".join(f"{k}={v}" for k, v in parameters.items())
36
+ return f"{type}/{subtype}; {params}"
37
+
38
+
39
+ def infer_content_encoding(content_type: str, content: bytes = b"") -> str:
40
+ """
41
+ Infer the encoding of content from the content-type header.
42
+ """
43
+ enc = None
44
+
45
+ # BOM has the highest priority
46
+ if content.startswith(b"\x00\x00\xfe\xff"):
47
+ enc = "utf-32be"
48
+ elif content.startswith(b"\xff\xfe\x00\x00"):
49
+ enc = "utf-32le"
50
+ elif content.startswith(b"\xfe\xff"):
51
+ enc = "utf-16be"
52
+ elif content.startswith(b"\xff\xfe"):
53
+ enc = "utf-16le"
54
+ elif content.startswith(b"\xef\xbb\xbf"):
55
+ # 'utf-8-sig' will strip the BOM on decode
56
+ enc = "utf-8-sig"
57
+ elif parsed_content_type := parse_content_type(content_type):
58
+ # Use the charset from the header if possible
59
+ enc = parsed_content_type[2].get("charset")
60
+
61
+ # Otherwise, infer the encoding
62
+ if not enc and "json" in content_type:
63
+ enc = "utf8"
64
+
65
+ if not enc and "html" in content_type:
66
+ meta_charset = re.search(
67
+ rb"""<meta[^>]+charset=['"]?([^'">]+)""", content, re.IGNORECASE
68
+ )
69
+ if meta_charset:
70
+ enc = meta_charset.group(1).decode("ascii", "ignore")
71
+ else:
72
+ # Fallback to utf8 for html
73
+ # Ref: https://html.spec.whatwg.org/multipage/parsing.html#determining-the-character-encoding
74
+ # > 9. [snip] the comprehensive UTF-8 encoding is suggested.
75
+ enc = "utf8"
76
+
77
+ if not enc and "xml" in content_type:
78
+ if xml_encoding := re.search(
79
+ rb"""<\?xml[^\?>]+encoding=['"]([^'"\?>]+)""", content, re.IGNORECASE
80
+ ):
81
+ enc = xml_encoding.group(1).decode("ascii", "ignore")
82
+ else:
83
+ # Fallback to utf8 for xml
84
+ # Ref: https://datatracker.ietf.org/doc/html/rfc7303#section-8.5
85
+ # > the XML processor [snip] to determine an encoding of UTF-8.
86
+ enc = "utf8"
87
+
88
+ if not enc and ("javascript" in content_type or "ecmascript" in content_type):
89
+ # Fallback to utf8 for javascript
90
+ # Ref: https://datatracker.ietf.org/doc/html/rfc9239#section-4.2
91
+ # > 3. Else, the character encoding scheme is assumed to be UTF-8
92
+ enc = "utf8"
93
+
94
+ if not enc and "text/css" in content_type:
95
+ # @charset rule must be the very first thing.
96
+ css_charset = re.match(rb"""@charset "([^"]+)";""", content, re.IGNORECASE)
97
+ if css_charset:
98
+ enc = css_charset.group(1).decode("ascii", "ignore")
99
+ else:
100
+ # Fallback to utf8 for css
101
+ # Ref: https://drafts.csswg.org/css-syntax/#determine-the-fallback-encoding
102
+ # > 4. Otherwise, return utf-8
103
+ enc = "utf8"
104
+
105
+ # Fallback to latin-1
106
+ if not enc:
107
+ enc = "latin-1"
108
+
109
+ # Use GB 18030 as the superset of GB2312 and GBK to fix common encoding problems on Chinese websites.
110
+ if enc.lower() in ("gb2312", "gbk"):
111
+ enc = "gb18030"
112
+
113
+ return enc
@@ -0,0 +1,21 @@
1
+ from .assemble import assemble_body
2
+ from .assemble import assemble_request
3
+ from .assemble import assemble_request_head
4
+ from .assemble import assemble_response
5
+ from .assemble import assemble_response_head
6
+ from .read import connection_close
7
+ from .read import expected_http_body_size
8
+ from .read import read_request_head
9
+ from .read import read_response_head
10
+
11
+ __all__ = [
12
+ "read_request_head",
13
+ "read_response_head",
14
+ "connection_close",
15
+ "expected_http_body_size",
16
+ "assemble_request",
17
+ "assemble_request_head",
18
+ "assemble_response",
19
+ "assemble_response_head",
20
+ "assemble_body",
21
+ ]
@@ -0,0 +1,99 @@
1
+ def assemble_request(request):
2
+ if request.data.content is None:
3
+ raise ValueError("Cannot assemble flow with missing content")
4
+ head = assemble_request_head(request)
5
+ body = b"".join(
6
+ assemble_body(
7
+ request.data.headers, [request.data.content], request.data.trailers
8
+ )
9
+ )
10
+ return head + body
11
+
12
+
13
+ def assemble_request_head(request):
14
+ first_line = _assemble_request_line(request.data)
15
+ headers = _assemble_request_headers(request.data)
16
+ return b"%s\r\n%s\r\n" % (first_line, headers)
17
+
18
+
19
+ def assemble_response(response):
20
+ if response.data.content is None:
21
+ raise ValueError("Cannot assemble flow with missing content")
22
+ head = assemble_response_head(response)
23
+ body = b"".join(
24
+ assemble_body(
25
+ response.data.headers, [response.data.content], response.data.trailers
26
+ )
27
+ )
28
+ return head + body
29
+
30
+
31
+ def assemble_response_head(response):
32
+ first_line = _assemble_response_line(response.data)
33
+ headers = _assemble_response_headers(response.data)
34
+ return b"%s\r\n%s\r\n" % (first_line, headers)
35
+
36
+
37
+ def assemble_body(headers, body_chunks, trailers):
38
+ if "chunked" in headers.get("transfer-encoding", "").lower():
39
+ for chunk in body_chunks:
40
+ if chunk:
41
+ yield b"%x\r\n%s\r\n" % (len(chunk), chunk)
42
+ if trailers:
43
+ yield b"0\r\n%s\r\n" % trailers
44
+ else:
45
+ yield b"0\r\n\r\n"
46
+ else:
47
+ if trailers:
48
+ raise ValueError(
49
+ "Sending HTTP/1.1 trailer headers requires transfer-encoding: chunked"
50
+ )
51
+ for chunk in body_chunks:
52
+ yield chunk
53
+
54
+
55
+ def _assemble_request_line(request_data):
56
+ """
57
+ Args:
58
+ request_data (mitmproxy.net.http.request.RequestData)
59
+ """
60
+ if request_data.method.upper() == b"CONNECT":
61
+ return b"%s %s %s" % (
62
+ request_data.method,
63
+ request_data.authority,
64
+ request_data.http_version,
65
+ )
66
+ elif request_data.authority:
67
+ return b"%s %s://%s%s %s" % (
68
+ request_data.method,
69
+ request_data.scheme,
70
+ request_data.authority,
71
+ request_data.path,
72
+ request_data.http_version,
73
+ )
74
+ else:
75
+ return b"%s %s %s" % (
76
+ request_data.method,
77
+ request_data.path,
78
+ request_data.http_version,
79
+ )
80
+
81
+
82
+ def _assemble_request_headers(request_data):
83
+ """
84
+ Args:
85
+ request_data (mitmproxy.net.http.request.RequestData)
86
+ """
87
+ return bytes(request_data.headers)
88
+
89
+
90
+ def _assemble_response_line(response_data):
91
+ return b"%s %d %s" % (
92
+ response_data.http_version,
93
+ response_data.status_code,
94
+ response_data.reason,
95
+ )
96
+
97
+
98
+ def _assemble_response_headers(response):
99
+ return bytes(response.headers)