cavisson-pythonagent 0.0.1__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 (180) hide show
  1. cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
  2. cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
  3. cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
  4. cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
  5. cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
  6. pythonagent/__init__.py +22 -0
  7. pythonagent/agent/__init__.py +219 -0
  8. pythonagent/agent/internal/__init__.py +1 -0
  9. pythonagent/agent/internal/agent.py +1651 -0
  10. pythonagent/agent/internal/framesinfo.py +152 -0
  11. pythonagent/agent/internal/heap_dump.py +39 -0
  12. pythonagent/agent/internal/intercept_module.py +70 -0
  13. pythonagent/agent/internal/logs.py +122 -0
  14. pythonagent/agent/internal/metadata/__init__.py +0 -0
  15. pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
  16. pythonagent/agent/internal/proc_compat.py +75 -0
  17. pythonagent/agent/internal/profile.py +47 -0
  18. pythonagent/agent/internal/provider.py +83 -0
  19. pythonagent/agent/internal/thread_dump.py +85 -0
  20. pythonagent/agent/internal/udp.py +245 -0
  21. pythonagent/agent/internal/udp_message.py +800 -0
  22. pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
  23. pythonagent/agent/probes/Instrumentation/find.py +243 -0
  24. pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
  25. pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
  26. pythonagent/agent/probes/Instrumentation/parser.py +129 -0
  27. pythonagent/agent/probes/__init__.py +219 -0
  28. pythonagent/agent/probes/base.py +303 -0
  29. pythonagent/agent/probes/cache/__init__.py +51 -0
  30. pythonagent/agent/probes/cache/redis.py +119 -0
  31. pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
  32. pythonagent/agent/probes/coroutines/__init__.py +1 -0
  33. pythonagent/agent/probes/coroutines/asyncio.py +63 -0
  34. pythonagent/agent/probes/elasticdb/__init__.py +7 -0
  35. pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
  36. pythonagent/agent/probes/frameworks/__init__.py +27 -0
  37. pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
  38. pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
  39. pythonagent/agent/probes/frameworks/aisess.py +151 -0
  40. pythonagent/agent/probes/frameworks/asgi.py +340 -0
  41. pythonagent/agent/probes/frameworks/bottle.py +27 -0
  42. pythonagent/agent/probes/frameworks/cherry.py +25 -0
  43. pythonagent/agent/probes/frameworks/django.py +128 -0
  44. pythonagent/agent/probes/frameworks/falcon.py +21 -0
  45. pythonagent/agent/probes/frameworks/fastapi.py +35 -0
  46. pythonagent/agent/probes/frameworks/flask.py +30 -0
  47. pythonagent/agent/probes/frameworks/pyramid.py +56 -0
  48. pythonagent/agent/probes/frameworks/test.py +108 -0
  49. pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
  50. pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
  51. pythonagent/agent/probes/frameworks/wsgi.py +353 -0
  52. pythonagent/agent/probes/grpc/__init__.py +76 -0
  53. pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
  54. pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
  55. pythonagent/agent/probes/havoc/__init__.py +0 -0
  56. pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
  57. pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
  58. pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
  59. pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
  60. pythonagent/agent/probes/http/__init__.py +49 -0
  61. pythonagent/agent/probes/http/aiohttp_client.py +59 -0
  62. pythonagent/agent/probes/http/boto.py +12 -0
  63. pythonagent/agent/probes/http/httplib.py +110 -0
  64. pythonagent/agent/probes/http/httpx_client.py +116 -0
  65. pythonagent/agent/probes/http/requests.py +15 -0
  66. pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
  67. pythonagent/agent/probes/http/urllib3.py +16 -0
  68. pythonagent/agent/probes/langchain/__init__.py +21 -0
  69. pythonagent/agent/probes/langchain/base_tool.py +95 -0
  70. pythonagent/agent/probes/langchain/langchain_community.py +136 -0
  71. pythonagent/agent/probes/langchain/langchain_core.py +32 -0
  72. pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
  73. pythonagent/agent/probes/logging/__init__.py +106 -0
  74. pythonagent/agent/probes/message_brokers/__init__.py +4 -0
  75. pythonagent/agent/probes/message_brokers/pika.py +126 -0
  76. pythonagent/agent/probes/mongodb/__init__.py +6 -0
  77. pythonagent/agent/probes/mongodb/pymongo.py +286 -0
  78. pythonagent/agent/probes/openai/__init__.py +3 -0
  79. pythonagent/agent/probes/openai/openai.py +797 -0
  80. pythonagent/agent/probes/span.py +101 -0
  81. pythonagent/agent/probes/sql/__init__.py +13 -0
  82. pythonagent/agent/probes/sql/botocores3.py +51 -0
  83. pythonagent/agent/probes/sql/dbapi.py +285 -0
  84. pythonagent/agent/probes/sql/dynamodb.py +90 -0
  85. pythonagent/agent/probes/sql/mysql_connector.py +24 -0
  86. pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
  87. pythonagent/agent/probes/sql/mysqldb.py +43 -0
  88. pythonagent/agent/probes/sql/psycopg2.py +174 -0
  89. pythonagent/agent/probes/sql/pymysql.py +25 -0
  90. pythonagent/bootstrap/__init__.py +0 -0
  91. pythonagent/bootstrap/cav_gunicorn.py +26 -0
  92. pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
  93. pythonagent/bootstrap/run.py +47 -0
  94. pythonagent/bootstrap/sitecustomize.py +287 -0
  95. pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
  96. pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
  97. pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
  98. pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
  99. pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
  100. pythonagent/config.py +279 -0
  101. pythonagent/find.py +72 -0
  102. pythonagent/find_mod_cls_name.py +54 -0
  103. pythonagent/lang.py +131 -0
  104. pythonagent/lib.py +91 -0
  105. pythonagent/main/__init__.py +0 -0
  106. pythonagent/main/pytrace/__init__.py +79 -0
  107. pythonagent/main/pytrace/commands/__init__.py +0 -0
  108. pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
  109. pythonagent/main/pytrace/commands/run.py +401 -0
  110. pythonagent/main/pytrace/pytrace.py +133 -0
  111. pythonagent/main/wsgi.py +6 -0
  112. pythonagent/main.py +27 -0
  113. pythonagent/run.py +46 -0
  114. pythonagent/sqins.py +8 -0
  115. pythonagent/test.py +73 -0
  116. pythonagent/utils.py +168 -0
  117. pythonagent/vendor/__init__.py +0 -0
  118. pythonagent/vendor/pympler/__init__.py +1 -0
  119. pythonagent/vendor/pympler/asizeof.py +2810 -0
  120. pythonagent/vendor/pympler/charts.py +62 -0
  121. pythonagent/vendor/pympler/classtracker.py +590 -0
  122. pythonagent/vendor/pympler/classtracker_stats.py +780 -0
  123. pythonagent/vendor/pympler/garbagegraph.py +80 -0
  124. pythonagent/vendor/pympler/mprofile.py +97 -0
  125. pythonagent/vendor/pympler/muppy.py +275 -0
  126. pythonagent/vendor/pympler/panels.py +115 -0
  127. pythonagent/vendor/pympler/process.py +238 -0
  128. pythonagent/vendor/pympler/py.typed +0 -0
  129. pythonagent/vendor/pympler/refbrowser.py +451 -0
  130. pythonagent/vendor/pympler/refgraph.py +350 -0
  131. pythonagent/vendor/pympler/summary.py +321 -0
  132. pythonagent/vendor/pympler/tracker.py +267 -0
  133. pythonagent/vendor/pympler/util/__init__.py +0 -0
  134. pythonagent/vendor/pympler/util/bottle.py +3809 -0
  135. pythonagent/vendor/pympler/util/compat.py +23 -0
  136. pythonagent/vendor/pympler/util/stringutils.py +77 -0
  137. pythonagent/vendor/pympler/web.py +346 -0
  138. pythonagent/vendor/werkzeug/__init__.py +20 -0
  139. pythonagent/vendor/werkzeug/_compat.py +228 -0
  140. pythonagent/vendor/werkzeug/_internal.py +473 -0
  141. pythonagent/vendor/werkzeug/_reloader.py +341 -0
  142. pythonagent/vendor/werkzeug/datastructures.py +3120 -0
  143. pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
  144. pythonagent/vendor/werkzeug/debug/console.py +218 -0
  145. pythonagent/vendor/werkzeug/debug/repr.py +297 -0
  146. pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
  147. pythonagent/vendor/werkzeug/exceptions.py +829 -0
  148. pythonagent/vendor/werkzeug/filesystem.py +64 -0
  149. pythonagent/vendor/werkzeug/formparser.py +584 -0
  150. pythonagent/vendor/werkzeug/http.py +1307 -0
  151. pythonagent/vendor/werkzeug/local.py +420 -0
  152. pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
  153. pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
  154. pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
  155. pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
  156. pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
  157. pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
  158. pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
  159. pythonagent/vendor/werkzeug/posixemulation.py +117 -0
  160. pythonagent/vendor/werkzeug/routing.py +2210 -0
  161. pythonagent/vendor/werkzeug/security.py +249 -0
  162. pythonagent/vendor/werkzeug/serving.py +1117 -0
  163. pythonagent/vendor/werkzeug/test.py +1123 -0
  164. pythonagent/vendor/werkzeug/testapp.py +241 -0
  165. pythonagent/vendor/werkzeug/urls.py +1138 -0
  166. pythonagent/vendor/werkzeug/useragents.py +202 -0
  167. pythonagent/vendor/werkzeug/utils.py +778 -0
  168. pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
  169. pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
  170. pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
  171. pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
  172. pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
  173. pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
  174. pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
  175. pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
  176. pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
  177. pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
  178. pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
  179. pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
  180. pythonagent/vendor/werkzeug/wsgi.py +1000 -0
@@ -0,0 +1,64 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ werkzeug.filesystem
4
+ ~~~~~~~~~~~~~~~~~~~
5
+
6
+ Various utilities for the local filesystem.
7
+
8
+ :copyright: 2007 Pallets
9
+ :license: BSD-3-Clause
10
+ """
11
+ import codecs
12
+ import sys
13
+ import warnings
14
+
15
+ # We do not trust traditional unixes.
16
+ has_likely_buggy_unicode_filesystem = (
17
+ sys.platform.startswith("linux") or "bsd" in sys.platform
18
+ )
19
+
20
+
21
+ def _is_ascii_encoding(encoding):
22
+ """Given an encoding this figures out if the encoding is actually ASCII (which
23
+ is something we don't actually want in most cases). This is necessary
24
+ because ASCII comes under many names such as ANSI_X3.4-1968.
25
+ """
26
+ if encoding is None:
27
+ return False
28
+ try:
29
+ return codecs.lookup(encoding).name == "ascii"
30
+ except LookupError:
31
+ return False
32
+
33
+
34
+ class BrokenFilesystemWarning(RuntimeWarning, UnicodeWarning):
35
+ """The warning used by Werkzeug to signal a broken filesystem. Will only be
36
+ used once per runtime."""
37
+
38
+
39
+ _warned_about_filesystem_encoding = False
40
+
41
+
42
+ def get_filesystem_encoding():
43
+ """Returns the filesystem encoding that should be used. Note that this is
44
+ different from the Python understanding of the filesystem encoding which
45
+ might be deeply flawed. Do not use this value against Python's unicode APIs
46
+ because it might be different. See :ref:`filesystem-encoding` for the exact
47
+ behavior.
48
+
49
+ The concept of a filesystem encoding in generally is not something you
50
+ should rely on. As such if you ever need to use this function except for
51
+ writing wrapper code reconsider.
52
+ """
53
+ global _warned_about_filesystem_encoding
54
+ rv = sys.getfilesystemencoding()
55
+ if has_likely_buggy_unicode_filesystem and not rv or _is_ascii_encoding(rv):
56
+ if not _warned_about_filesystem_encoding:
57
+ warnings.warn(
58
+ "Detected a misconfigured UNIX filesystem: Will use"
59
+ " UTF-8 as filesystem encoding instead of {0!r}".format(rv),
60
+ BrokenFilesystemWarning,
61
+ )
62
+ _warned_about_filesystem_encoding = True
63
+ return "utf-8"
64
+ return rv
@@ -0,0 +1,584 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ werkzeug.formparser
4
+ ~~~~~~~~~~~~~~~~~~~
5
+
6
+ This module implements the form parsing. It supports url-encoded forms
7
+ as well as non-nested multipart uploads.
8
+
9
+ :copyright: 2007 Pallets
10
+ :license: BSD-3-Clause
11
+ """
12
+ import codecs
13
+ import re
14
+ from functools import update_wrapper
15
+ from itertools import chain
16
+ from itertools import repeat
17
+ from itertools import tee
18
+
19
+ from . import exceptions
20
+ from ._compat import BytesIO
21
+ from ._compat import text_type
22
+ from ._compat import to_native
23
+ from .datastructures import FileStorage
24
+ from .datastructures import Headers
25
+ from .datastructures import MultiDict
26
+ from .http import parse_options_header
27
+ from .urls import url_decode_stream
28
+ from .wsgi import get_content_length
29
+ from .wsgi import get_input_stream
30
+ from .wsgi import make_line_iter
31
+
32
+ # there are some platforms where SpooledTemporaryFile is not available.
33
+ # In that case we need to provide a fallback.
34
+ try:
35
+ from tempfile import SpooledTemporaryFile
36
+ except ImportError:
37
+ from tempfile import TemporaryFile
38
+
39
+ SpooledTemporaryFile = None
40
+
41
+
42
+ #: an iterator that yields empty strings
43
+ _empty_string_iter = repeat("")
44
+
45
+ #: a regular expression for multipart boundaries
46
+ _multipart_boundary_re = re.compile("^[ -~]{0,200}[!-~]$")
47
+
48
+ #: supported http encodings that are also available in python we support
49
+ #: for multipart messages.
50
+ _supported_multipart_encodings = frozenset(["base64", "quoted-printable"])
51
+
52
+
53
+ def default_stream_factory(
54
+ total_content_length, filename, content_type, content_length=None
55
+ ):
56
+ """The stream factory that is used per default."""
57
+ max_size = 1024 * 500
58
+ if SpooledTemporaryFile is not None:
59
+ return SpooledTemporaryFile(max_size=max_size, mode="wb+")
60
+ if total_content_length is None or total_content_length > max_size:
61
+ return TemporaryFile("wb+")
62
+ return BytesIO()
63
+
64
+
65
+ def parse_form_data(
66
+ environ,
67
+ stream_factory=None,
68
+ charset="utf-8",
69
+ errors="replace",
70
+ max_form_memory_size=None,
71
+ max_content_length=None,
72
+ cls=None,
73
+ silent=True,
74
+ ):
75
+ """Parse the form data in the environ and return it as tuple in the form
76
+ ``(stream, form, files)``. You should only call this method if the
77
+ transport method is `POST`, `PUT`, or `PATCH`.
78
+
79
+ If the mimetype of the data transmitted is `multipart/form-data` the
80
+ files multidict will be filled with `FileStorage` objects. If the
81
+ mimetype is unknown the input stream is wrapped and returned as first
82
+ argument, else the stream is empty.
83
+
84
+ This is a shortcut for the common usage of :class:`FormDataParser`.
85
+
86
+ Have a look at :ref:`dealing-with-request-data` for more details.
87
+
88
+ .. versionadded:: 0.5
89
+ The `max_form_memory_size`, `max_content_length` and
90
+ `cls` parameters were added.
91
+
92
+ .. versionadded:: 0.5.1
93
+ The optional `silent` flag was added.
94
+
95
+ :param environ: the WSGI environment to be used for parsing.
96
+ :param stream_factory: An optional callable that returns a new read and
97
+ writeable file descriptor. This callable works
98
+ the same as :meth:`~BaseResponse._get_file_stream`.
99
+ :param charset: The character set for URL and url encoded form data.
100
+ :param errors: The encoding error behavior.
101
+ :param max_form_memory_size: the maximum number of bytes to be accepted for
102
+ in-memory stored form data. If the data
103
+ exceeds the value specified an
104
+ :exc:`~exceptions.RequestEntityTooLarge`
105
+ exception is raised.
106
+ :param max_content_length: If this is provided and the transmitted data
107
+ is longer than this value an
108
+ :exc:`~exceptions.RequestEntityTooLarge`
109
+ exception is raised.
110
+ :param cls: an optional dict class to use. If this is not specified
111
+ or `None` the default :class:`MultiDict` is used.
112
+ :param silent: If set to False parsing errors will not be caught.
113
+ :return: A tuple in the form ``(stream, form, files)``.
114
+ """
115
+ return FormDataParser(
116
+ stream_factory,
117
+ charset,
118
+ errors,
119
+ max_form_memory_size,
120
+ max_content_length,
121
+ cls,
122
+ silent,
123
+ ).parse_from_environ(environ)
124
+
125
+
126
+ def exhaust_stream(f):
127
+ """Helper decorator for methods that exhausts the stream on return."""
128
+
129
+ def wrapper(self, stream, *args, **kwargs):
130
+ try:
131
+ return f(self, stream, *args, **kwargs)
132
+ finally:
133
+ exhaust = getattr(stream, "exhaust", None)
134
+ if exhaust is not None:
135
+ exhaust()
136
+ else:
137
+ while 1:
138
+ chunk = stream.read(1024 * 64)
139
+ if not chunk:
140
+ break
141
+
142
+ return update_wrapper(wrapper, f)
143
+
144
+
145
+ class FormDataParser(object):
146
+ """This class implements parsing of form data for Werkzeug. By itself
147
+ it can parse multipart and url encoded form data. It can be subclassed
148
+ and extended but for most mimetypes it is a better idea to use the
149
+ untouched stream and expose it as separate attributes on a request
150
+ object.
151
+
152
+ .. versionadded:: 0.8
153
+
154
+ :param stream_factory: An optional callable that returns a new read and
155
+ writeable file descriptor. This callable works
156
+ the same as :meth:`~BaseResponse._get_file_stream`.
157
+ :param charset: The character set for URL and url encoded form data.
158
+ :param errors: The encoding error behavior.
159
+ :param max_form_memory_size: the maximum number of bytes to be accepted for
160
+ in-memory stored form data. If the data
161
+ exceeds the value specified an
162
+ :exc:`~exceptions.RequestEntityTooLarge`
163
+ exception is raised.
164
+ :param max_content_length: If this is provided and the transmitted data
165
+ is longer than this value an
166
+ :exc:`~exceptions.RequestEntityTooLarge`
167
+ exception is raised.
168
+ :param cls: an optional dict class to use. If this is not specified
169
+ or `None` the default :class:`MultiDict` is used.
170
+ :param silent: If set to False parsing errors will not be caught.
171
+ """
172
+
173
+ def __init__(
174
+ self,
175
+ stream_factory=None,
176
+ charset="utf-8",
177
+ errors="replace",
178
+ max_form_memory_size=None,
179
+ max_content_length=None,
180
+ cls=None,
181
+ silent=True,
182
+ ):
183
+ if stream_factory is None:
184
+ stream_factory = default_stream_factory
185
+ self.stream_factory = stream_factory
186
+ self.charset = charset
187
+ self.errors = errors
188
+ self.max_form_memory_size = max_form_memory_size
189
+ self.max_content_length = max_content_length
190
+ if cls is None:
191
+ cls = MultiDict
192
+ self.cls = cls
193
+ self.silent = silent
194
+
195
+ def get_parse_func(self, mimetype, options):
196
+ return self.parse_functions.get(mimetype)
197
+
198
+ def parse_from_environ(self, environ):
199
+ """Parses the information from the environment as form data.
200
+
201
+ :param environ: the WSGI environment to be used for parsing.
202
+ :return: A tuple in the form ``(stream, form, files)``.
203
+ """
204
+ content_type = environ.get("CONTENT_TYPE", "")
205
+ content_length = get_content_length(environ)
206
+ mimetype, options = parse_options_header(content_type)
207
+ return self.parse(get_input_stream(environ), mimetype, content_length, options)
208
+
209
+ def parse(self, stream, mimetype, content_length, options=None):
210
+ """Parses the information from the given stream, mimetype,
211
+ content length and mimetype parameters.
212
+
213
+ :param stream: an input stream
214
+ :param mimetype: the mimetype of the data
215
+ :param content_length: the content length of the incoming data
216
+ :param options: optional mimetype parameters (used for
217
+ the multipart boundary for instance)
218
+ :return: A tuple in the form ``(stream, form, files)``.
219
+ """
220
+ if (
221
+ self.max_content_length is not None
222
+ and content_length is not None
223
+ and content_length > self.max_content_length
224
+ ):
225
+ raise exceptions.RequestEntityTooLarge()
226
+ if options is None:
227
+ options = {}
228
+
229
+ parse_func = self.get_parse_func(mimetype, options)
230
+ if parse_func is not None:
231
+ try:
232
+ return parse_func(self, stream, mimetype, content_length, options)
233
+ except ValueError:
234
+ if not self.silent:
235
+ raise
236
+
237
+ return stream, self.cls(), self.cls()
238
+
239
+ @exhaust_stream
240
+ def _parse_multipart(self, stream, mimetype, content_length, options):
241
+ parser = MultiPartParser(
242
+ self.stream_factory,
243
+ self.charset,
244
+ self.errors,
245
+ max_form_memory_size=self.max_form_memory_size,
246
+ cls=self.cls,
247
+ )
248
+ boundary = options.get("boundary")
249
+ if boundary is None:
250
+ raise ValueError("Missing boundary")
251
+ if isinstance(boundary, text_type):
252
+ boundary = boundary.encode("ascii")
253
+ form, files = parser.parse(stream, boundary, content_length)
254
+ return stream, form, files
255
+
256
+ @exhaust_stream
257
+ def _parse_urlencoded(self, stream, mimetype, content_length, options):
258
+ if (
259
+ self.max_form_memory_size is not None
260
+ and content_length is not None
261
+ and content_length > self.max_form_memory_size
262
+ ):
263
+ raise exceptions.RequestEntityTooLarge()
264
+ form = url_decode_stream(stream, self.charset, errors=self.errors, cls=self.cls)
265
+ return stream, form, self.cls()
266
+
267
+ #: mapping of mimetypes to parsing functions
268
+ parse_functions = {
269
+ "multipart/form-data": _parse_multipart,
270
+ "application/x-www-form-urlencoded": _parse_urlencoded,
271
+ "application/x-url-encoded": _parse_urlencoded,
272
+ }
273
+
274
+
275
+ def is_valid_multipart_boundary(boundary):
276
+ """Checks if the string given is a valid multipart boundary."""
277
+ return _multipart_boundary_re.match(boundary) is not None
278
+
279
+
280
+ def _line_parse(line):
281
+ """Removes line ending characters and returns a tuple (`stripped_line`,
282
+ `is_terminated`).
283
+ """
284
+ if line[-2:] in ["\r\n", b"\r\n"]:
285
+ return line[:-2], True
286
+ elif line[-1:] in ["\r", "\n", b"\r", b"\n"]:
287
+ return line[:-1], True
288
+ return line, False
289
+
290
+
291
+ def parse_multipart_headers(iterable):
292
+ """Parses multipart headers from an iterable that yields lines (including
293
+ the trailing newline symbol). The iterable has to be newline terminated.
294
+
295
+ The iterable will stop at the line where the headers ended so it can be
296
+ further consumed.
297
+
298
+ :param iterable: iterable of strings that are newline terminated
299
+ """
300
+ result = []
301
+ for line in iterable:
302
+ line = to_native(line)
303
+ line, line_terminated = _line_parse(line)
304
+ if not line_terminated:
305
+ raise ValueError("unexpected end of line in multipart header")
306
+ if not line:
307
+ break
308
+ elif line[0] in " \t" and result:
309
+ key, value = result[-1]
310
+ result[-1] = (key, value + "\n " + line[1:])
311
+ else:
312
+ parts = line.split(":", 1)
313
+ if len(parts) == 2:
314
+ result.append((parts[0].strip(), parts[1].strip()))
315
+
316
+ # we link the list to the headers, no need to create a copy, the
317
+ # list was not shared anyways.
318
+ return Headers(result)
319
+
320
+
321
+ _begin_form = "begin_form"
322
+ _begin_file = "begin_file"
323
+ _cont = "cont"
324
+ _end = "end"
325
+
326
+
327
+ class MultiPartParser(object):
328
+ def __init__(
329
+ self,
330
+ stream_factory=None,
331
+ charset="utf-8",
332
+ errors="replace",
333
+ max_form_memory_size=None,
334
+ cls=None,
335
+ buffer_size=64 * 1024,
336
+ ):
337
+ self.charset = charset
338
+ self.errors = errors
339
+ self.max_form_memory_size = max_form_memory_size
340
+ self.stream_factory = (
341
+ default_stream_factory if stream_factory is None else stream_factory
342
+ )
343
+ self.cls = MultiDict if cls is None else cls
344
+
345
+ # make sure the buffer size is divisible by four so that we can base64
346
+ # decode chunk by chunk
347
+ assert buffer_size % 4 == 0, "buffer size has to be divisible by 4"
348
+ # also the buffer size has to be at least 1024 bytes long or long headers
349
+ # will freak out the system
350
+ assert buffer_size >= 1024, "buffer size has to be at least 1KB"
351
+
352
+ self.buffer_size = buffer_size
353
+
354
+ def _fix_ie_filename(self, filename):
355
+ """Internet Explorer 6 transmits the full file name if a file is
356
+ uploaded. This function strips the full path if it thinks the
357
+ filename is Windows-like absolute.
358
+ """
359
+ if filename[1:3] == ":\\" or filename[:2] == "\\\\":
360
+ return filename.split("\\")[-1]
361
+ return filename
362
+
363
+ def _find_terminator(self, iterator):
364
+ """The terminator might have some additional newlines before it.
365
+ There is at least one application that sends additional newlines
366
+ before headers (the python setuptools package).
367
+ """
368
+ for line in iterator:
369
+ if not line:
370
+ break
371
+ line = line.strip()
372
+ if line:
373
+ return line
374
+ return b""
375
+
376
+ def fail(self, message):
377
+ raise ValueError(message)
378
+
379
+ def get_part_encoding(self, headers):
380
+ transfer_encoding = headers.get("content-transfer-encoding")
381
+ if (
382
+ transfer_encoding is not None
383
+ and transfer_encoding in _supported_multipart_encodings
384
+ ):
385
+ return transfer_encoding
386
+
387
+ def get_part_charset(self, headers):
388
+ # Figure out input charset for current part
389
+ content_type = headers.get("content-type")
390
+ if content_type:
391
+ mimetype, ct_params = parse_options_header(content_type)
392
+ return ct_params.get("charset", self.charset)
393
+ return self.charset
394
+
395
+ def start_file_streaming(self, filename, headers, total_content_length):
396
+ if isinstance(filename, bytes):
397
+ filename = filename.decode(self.charset, self.errors)
398
+ filename = self._fix_ie_filename(filename)
399
+ content_type = headers.get("content-type")
400
+ try:
401
+ content_length = int(headers["content-length"])
402
+ except (KeyError, ValueError):
403
+ content_length = 0
404
+ container = self.stream_factory(
405
+ total_content_length=total_content_length,
406
+ filename=filename,
407
+ content_type=content_type,
408
+ content_length=content_length,
409
+ )
410
+ return filename, container
411
+
412
+ def in_memory_threshold_reached(self, bytes):
413
+ raise exceptions.RequestEntityTooLarge()
414
+
415
+ def validate_boundary(self, boundary):
416
+ if not boundary:
417
+ self.fail("Missing boundary")
418
+ if not is_valid_multipart_boundary(boundary):
419
+ self.fail("Invalid boundary: %s" % boundary)
420
+ if len(boundary) > self.buffer_size: # pragma: no cover
421
+ # this should never happen because we check for a minimum size
422
+ # of 1024 and boundaries may not be longer than 200. The only
423
+ # situation when this happens is for non debug builds where
424
+ # the assert is skipped.
425
+ self.fail("Boundary longer than buffer size")
426
+
427
+ def parse_lines(self, file, boundary, content_length, cap_at_buffer=True):
428
+ """Generate parts of
429
+ ``('begin_form', (headers, name))``
430
+ ``('begin_file', (headers, name, filename))``
431
+ ``('cont', bytestring)``
432
+ ``('end', None)``
433
+
434
+ Always obeys the grammar
435
+ parts = ( begin_form cont* end |
436
+ begin_file cont* end )*
437
+ """
438
+ next_part = b"--" + boundary
439
+ last_part = next_part + b"--"
440
+
441
+ iterator = chain(
442
+ make_line_iter(
443
+ file,
444
+ limit=content_length,
445
+ buffer_size=self.buffer_size,
446
+ cap_at_buffer=cap_at_buffer,
447
+ ),
448
+ _empty_string_iter,
449
+ )
450
+
451
+ terminator = self._find_terminator(iterator)
452
+
453
+ if terminator == last_part:
454
+ return
455
+ elif terminator != next_part:
456
+ self.fail("Expected boundary at start of multipart data")
457
+
458
+ while terminator != last_part:
459
+ headers = parse_multipart_headers(iterator)
460
+
461
+ disposition = headers.get("content-disposition")
462
+ if disposition is None:
463
+ self.fail("Missing Content-Disposition header")
464
+ disposition, extra = parse_options_header(disposition)
465
+ transfer_encoding = self.get_part_encoding(headers)
466
+ name = extra.get("name")
467
+ filename = extra.get("filename")
468
+
469
+ # if no content type is given we stream into memory. A list is
470
+ # used as a temporary container.
471
+ if filename is None:
472
+ yield _begin_form, (headers, name)
473
+
474
+ # otherwise we parse the rest of the headers and ask the stream
475
+ # factory for something we can write in.
476
+ else:
477
+ yield _begin_file, (headers, name, filename)
478
+
479
+ buf = b""
480
+ for line in iterator:
481
+ if not line:
482
+ self.fail("unexpected end of stream")
483
+
484
+ if line[:2] == b"--":
485
+ terminator = line.rstrip()
486
+ if terminator in (next_part, last_part):
487
+ break
488
+
489
+ if transfer_encoding is not None:
490
+ if transfer_encoding == "base64":
491
+ transfer_encoding = "base64_codec"
492
+ try:
493
+ line = codecs.decode(line, transfer_encoding)
494
+ except Exception:
495
+ self.fail("could not decode transfer encoded chunk")
496
+
497
+ # we have something in the buffer from the last iteration.
498
+ # this is usually a newline delimiter.
499
+ if buf:
500
+ yield _cont, buf
501
+ buf = b""
502
+
503
+ # If the line ends with windows CRLF we write everything except
504
+ # the last two bytes. In all other cases however we write
505
+ # everything except the last byte. If it was a newline, that's
506
+ # fine, otherwise it does not matter because we will write it
507
+ # the next iteration. this ensures we do not write the
508
+ # final newline into the stream. That way we do not have to
509
+ # truncate the stream. However we do have to make sure that
510
+ # if something else than a newline is in there we write it
511
+ # out.
512
+ if line[-2:] == b"\r\n":
513
+ buf = b"\r\n"
514
+ cutoff = -2
515
+ else:
516
+ buf = line[-1:]
517
+ cutoff = -1
518
+ yield _cont, line[:cutoff]
519
+
520
+ else: # pragma: no cover
521
+ raise ValueError("unexpected end of part")
522
+
523
+ # if we have a leftover in the buffer that is not a newline
524
+ # character we have to flush it, otherwise we will chop of
525
+ # certain values.
526
+ if buf not in (b"", b"\r", b"\n", b"\r\n"):
527
+ yield _cont, buf
528
+
529
+ yield _end, None
530
+
531
+ def parse_parts(self, file, boundary, content_length):
532
+ """Generate ``('file', (name, val))`` and
533
+ ``('form', (name, val))`` parts.
534
+ """
535
+ in_memory = 0
536
+
537
+ for ellt, ell in self.parse_lines(file, boundary, content_length):
538
+ if ellt == _begin_file:
539
+ headers, name, filename = ell
540
+ is_file = True
541
+ guard_memory = False
542
+ filename, container = self.start_file_streaming(
543
+ filename, headers, content_length
544
+ )
545
+ _write = container.write
546
+
547
+ elif ellt == _begin_form:
548
+ headers, name = ell
549
+ is_file = False
550
+ container = []
551
+ _write = container.append
552
+ guard_memory = self.max_form_memory_size is not None
553
+
554
+ elif ellt == _cont:
555
+ _write(ell)
556
+ # if we write into memory and there is a memory size limit we
557
+ # count the number of bytes in memory and raise an exception if
558
+ # there is too much data in memory.
559
+ if guard_memory:
560
+ in_memory += len(ell)
561
+ if in_memory > self.max_form_memory_size:
562
+ self.in_memory_threshold_reached(in_memory)
563
+
564
+ elif ellt == _end:
565
+ if is_file:
566
+ container.seek(0)
567
+ yield (
568
+ "file",
569
+ (name, FileStorage(container, filename, name, headers=headers)),
570
+ )
571
+ else:
572
+ part_charset = self.get_part_charset(headers)
573
+ yield (
574
+ "form",
575
+ (name, b"".join(container).decode(part_charset, self.errors)),
576
+ )
577
+
578
+ def parse(self, file, boundary, content_length):
579
+ formstream, filestream = tee(
580
+ self.parse_parts(file, boundary, content_length), 2
581
+ )
582
+ form = (p[1] for p in formstream if p[0] == "form")
583
+ files = (p[1] for p in filestream if p[0] == "file")
584
+ return self.cls(form), self.cls(files)