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,498 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ werkzeug.debug
4
+ ~~~~~~~~~~~~~~
5
+
6
+ WSGI application traceback debugger.
7
+
8
+ :copyright: 2007 Pallets
9
+ :license: BSD-3-Clause
10
+ """
11
+ import getpass
12
+ import hashlib
13
+ import json
14
+ import mimetypes
15
+ import os
16
+ import pkgutil
17
+ import re
18
+ import sys
19
+ import time
20
+ import uuid
21
+ from itertools import chain
22
+ from os.path import basename
23
+ from os.path import join
24
+
25
+ from .._compat import text_type
26
+ from .._internal import _log
27
+ from ..http import parse_cookie
28
+ from ..security import gen_salt
29
+ from ..wrappers import BaseRequest as Request
30
+ from ..wrappers import BaseResponse as Response
31
+ from .console import Console
32
+ from .tbtools import get_current_traceback
33
+ from .tbtools import render_console_html
34
+
35
+ # A week
36
+ PIN_TIME = 60 * 60 * 24 * 7
37
+
38
+
39
+ def hash_pin(pin):
40
+ if isinstance(pin, text_type):
41
+ pin = pin.encode("utf-8", "replace")
42
+ return hashlib.md5(pin + b"shittysalt").hexdigest()[:12]
43
+
44
+
45
+ _machine_id = None
46
+
47
+
48
+ def get_machine_id():
49
+ global _machine_id
50
+
51
+ if _machine_id is not None:
52
+ return _machine_id
53
+
54
+ def _generate():
55
+ linux = b""
56
+
57
+ # machine-id is stable across boots, boot_id is not.
58
+ for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
59
+ try:
60
+ with open(filename, "rb") as f:
61
+ value = f.readline().strip()
62
+ except IOError:
63
+ continue
64
+
65
+ if value:
66
+ linux += value
67
+ break
68
+
69
+ # Containers share the same machine id, add some cgroup
70
+ # information. This is used outside containers too but should be
71
+ # relatively stable across boots.
72
+ try:
73
+ with open("/proc/self/cgroup", "rb") as f:
74
+ linux += f.readline().strip().rpartition(b"/")[2]
75
+ except IOError:
76
+ pass
77
+
78
+ if linux:
79
+ return linux
80
+
81
+ # On OS X, use ioreg to get the computer's serial number.
82
+ try:
83
+ # subprocess may not be available, e.g. Google App Engine
84
+ # https://github.com/pallets/werkzeug/issues/925
85
+ from subprocess import Popen, PIPE
86
+
87
+ dump = Popen(
88
+ ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"], stdout=PIPE
89
+ ).communicate()[0]
90
+ match = re.search(b'"serial-number" = <([^>]+)', dump)
91
+
92
+ if match is not None:
93
+ return match.group(1)
94
+ except (OSError, ImportError):
95
+ pass
96
+
97
+ # On Windows, use winreg to get the machine guid.
98
+ try:
99
+ import winreg as wr
100
+ except ImportError:
101
+ try:
102
+ import _winreg as wr
103
+ except ImportError:
104
+ wr = None
105
+
106
+ if wr is not None:
107
+ try:
108
+ with wr.OpenKey(
109
+ wr.HKEY_LOCAL_MACHINE,
110
+ "SOFTWARE\\Microsoft\\Cryptography",
111
+ 0,
112
+ wr.KEY_READ | wr.KEY_WOW64_64KEY,
113
+ ) as rk:
114
+ guid, guid_type = wr.QueryValueEx(rk, "MachineGuid")
115
+
116
+ if guid_type == wr.REG_SZ:
117
+ return guid.encode("utf-8")
118
+
119
+ return guid
120
+ except WindowsError:
121
+ pass
122
+
123
+ _machine_id = _generate()
124
+ return _machine_id
125
+
126
+
127
+ class _ConsoleFrame(object):
128
+ """Helper class so that we can reuse the frame console code for the
129
+ standalone console.
130
+ """
131
+
132
+ def __init__(self, namespace):
133
+ self.console = Console(namespace)
134
+ self.id = 0
135
+
136
+
137
+ def get_pin_and_cookie_name(app):
138
+ """Given an application object this returns a semi-stable 9 digit pin
139
+ code and a random key. The hope is that this is stable between
140
+ restarts to not make debugging particularly frustrating. If the pin
141
+ was forcefully disabled this returns `None`.
142
+
143
+ Second item in the resulting tuple is the cookie name for remembering.
144
+ """
145
+ pin = os.environ.get("WERKZEUG_DEBUG_PIN")
146
+ rv = None
147
+ num = None
148
+
149
+ # Pin was explicitly disabled
150
+ if pin == "off":
151
+ return None, None
152
+
153
+ # Pin was provided explicitly
154
+ if pin is not None and pin.replace("-", "").isdigit():
155
+ # If there are separators in the pin, return it directly
156
+ if "-" in pin:
157
+ rv = pin
158
+ else:
159
+ num = pin
160
+
161
+ modname = getattr(app, "__module__", app.__class__.__module__)
162
+
163
+ try:
164
+ # getuser imports the pwd module, which does not exist in Google
165
+ # App Engine. It may also raise a KeyError if the UID does not
166
+ # have a username, such as in Docker.
167
+ username = getpass.getuser()
168
+ except (ImportError, KeyError):
169
+ username = None
170
+
171
+ mod = sys.modules.get(modname)
172
+
173
+ # This information only exists to make the cookie unique on the
174
+ # computer, not as a security feature.
175
+ probably_public_bits = [
176
+ username,
177
+ modname,
178
+ getattr(app, "__name__", app.__class__.__name__),
179
+ getattr(mod, "__file__", None),
180
+ ]
181
+
182
+ # This information is here to make it harder for an attacker to
183
+ # guess the cookie name. They are unlikely to be contained anywhere
184
+ # within the unauthenticated debug page.
185
+ private_bits = [str(uuid.getnode()), get_machine_id()]
186
+
187
+ h = hashlib.md5()
188
+ for bit in chain(probably_public_bits, private_bits):
189
+ if not bit:
190
+ continue
191
+ if isinstance(bit, text_type):
192
+ bit = bit.encode("utf-8")
193
+ h.update(bit)
194
+ h.update(b"cookiesalt")
195
+
196
+ cookie_name = "__wzd" + h.hexdigest()[:20]
197
+
198
+ # If we need to generate a pin we salt it a bit more so that we don't
199
+ # end up with the same value and generate out 9 digits
200
+ if num is None:
201
+ h.update(b"pinsalt")
202
+ num = ("%09d" % int(h.hexdigest(), 16))[:9]
203
+
204
+ # Format the pincode in groups of digits for easier remembering if
205
+ # we don't have a result yet.
206
+ if rv is None:
207
+ for group_size in 5, 4, 3:
208
+ if len(num) % group_size == 0:
209
+ rv = "-".join(
210
+ num[x : x + group_size].rjust(group_size, "0")
211
+ for x in range(0, len(num), group_size)
212
+ )
213
+ break
214
+ else:
215
+ rv = num
216
+
217
+ return rv, cookie_name
218
+
219
+
220
+ class DebuggedApplication(object):
221
+ """Enables debugging support for a given application::
222
+
223
+ from pythonagent.vendor.werkzeug.debug import DebuggedApplication
224
+ from myapp import app
225
+ app = DebuggedApplication(app, evalex=True)
226
+
227
+ The `evalex` keyword argument allows evaluating expressions in a
228
+ traceback's frame context.
229
+
230
+ :param app: the WSGI application to run debugged.
231
+ :param evalex: enable exception evaluation feature (interactive
232
+ debugging). This requires a non-forking server.
233
+ :param request_key: The key that points to the request object in ths
234
+ environment. This parameter is ignored in current
235
+ versions.
236
+ :param console_path: the URL for a general purpose console.
237
+ :param console_init_func: the function that is executed before starting
238
+ the general purpose console. The return value
239
+ is used as initial namespace.
240
+ :param show_hidden_frames: by default hidden traceback frames are skipped.
241
+ You can show them by setting this parameter
242
+ to `True`.
243
+ :param pin_security: can be used to disable the pin based security system.
244
+ :param pin_logging: enables the logging of the pin system.
245
+ """
246
+
247
+ def __init__(
248
+ self,
249
+ app,
250
+ evalex=False,
251
+ request_key="werkzeug.request",
252
+ console_path="/console",
253
+ console_init_func=None,
254
+ show_hidden_frames=False,
255
+ pin_security=True,
256
+ pin_logging=True,
257
+ ):
258
+ if not console_init_func:
259
+ console_init_func = None
260
+ self.app = app
261
+ self.evalex = evalex
262
+ self.frames = {}
263
+ self.tracebacks = {}
264
+ self.request_key = request_key
265
+ self.console_path = console_path
266
+ self.console_init_func = console_init_func
267
+ self.show_hidden_frames = show_hidden_frames
268
+ self.secret = gen_salt(20)
269
+ self._failed_pin_auth = 0
270
+
271
+ self.pin_logging = pin_logging
272
+ if pin_security:
273
+ # Print out the pin for the debugger on standard out.
274
+ if os.environ.get("WERKZEUG_RUN_MAIN") == "true" and pin_logging:
275
+ _log("warning", " * Debugger is active!")
276
+ if self.pin is None:
277
+ _log("warning", " * Debugger PIN disabled. DEBUGGER UNSECURED!")
278
+ else:
279
+ _log("info", " * Debugger PIN: %s" % self.pin)
280
+ else:
281
+ self.pin = None
282
+
283
+ @property
284
+ def pin(self):
285
+ if not hasattr(self, "_pin"):
286
+ self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
287
+ return self._pin
288
+
289
+ @pin.setter
290
+ def pin(self, value):
291
+ self._pin = value
292
+
293
+ @property
294
+ def pin_cookie_name(self):
295
+ """The name of the pin cookie."""
296
+ if not hasattr(self, "_pin_cookie"):
297
+ self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
298
+ return self._pin_cookie
299
+
300
+ def debug_application(self, environ, start_response):
301
+ """Run the application and conserve the traceback frames."""
302
+ app_iter = None
303
+ try:
304
+ app_iter = self.app(environ, start_response)
305
+ for item in app_iter:
306
+ yield item
307
+ if hasattr(app_iter, "close"):
308
+ app_iter.close()
309
+ except Exception:
310
+ if hasattr(app_iter, "close"):
311
+ app_iter.close()
312
+ traceback = get_current_traceback(
313
+ skip=1,
314
+ show_hidden_frames=self.show_hidden_frames,
315
+ ignore_system_exceptions=True,
316
+ )
317
+ for frame in traceback.frames:
318
+ self.frames[frame.id] = frame
319
+ self.tracebacks[traceback.id] = traceback
320
+
321
+ try:
322
+ start_response(
323
+ "500 INTERNAL SERVER ERROR",
324
+ [
325
+ ("Content-Type", "text/html; charset=utf-8"),
326
+ # Disable Chrome's XSS protection, the debug
327
+ # output can cause false-positives.
328
+ ("X-XSS-Protection", "0"),
329
+ ],
330
+ )
331
+ except Exception:
332
+ # if we end up here there has been output but an error
333
+ # occurred. in that situation we can do nothing fancy any
334
+ # more, better log something into the error log and fall
335
+ # back gracefully.
336
+ environ["wsgi.errors"].write(
337
+ "Debugging middleware caught exception in streamed "
338
+ "response at a point where response headers were already "
339
+ "sent.\n"
340
+ )
341
+ else:
342
+ is_trusted = bool(self.check_pin_trust(environ))
343
+ yield traceback.render_full(
344
+ evalex=self.evalex, evalex_trusted=is_trusted, secret=self.secret
345
+ ).encode("utf-8", "replace")
346
+
347
+ traceback.log(environ["wsgi.errors"])
348
+
349
+ def execute_command(self, request, command, frame):
350
+ """Execute a command in a console."""
351
+ return Response(frame.console.eval(command), mimetype="text/html")
352
+
353
+ def display_console(self, request):
354
+ """Display a standalone shell."""
355
+ if 0 not in self.frames:
356
+ if self.console_init_func is None:
357
+ ns = {}
358
+ else:
359
+ ns = dict(self.console_init_func())
360
+ ns.setdefault("app", self.app)
361
+ self.frames[0] = _ConsoleFrame(ns)
362
+ is_trusted = bool(self.check_pin_trust(request.environ))
363
+ return Response(
364
+ render_console_html(secret=self.secret, evalex_trusted=is_trusted),
365
+ mimetype="text/html",
366
+ )
367
+
368
+ def paste_traceback(self, request, traceback):
369
+ """Paste the traceback and return a JSON response."""
370
+ rv = traceback.paste()
371
+ return Response(json.dumps(rv), mimetype="application/json")
372
+
373
+ def get_resource(self, request, filename):
374
+ """Return a static resource from the shared folder."""
375
+ filename = join("shared", basename(filename))
376
+ try:
377
+ data = pkgutil.get_data(__package__, filename)
378
+ except OSError:
379
+ data = None
380
+ if data is not None:
381
+ mimetype = mimetypes.guess_type(filename)[0] or "application/octet-stream"
382
+ return Response(data, mimetype=mimetype)
383
+ return Response("Not Found", status=404)
384
+
385
+ def check_pin_trust(self, environ):
386
+ """Checks if the request passed the pin test. This returns `True` if the
387
+ request is trusted on a pin/cookie basis and returns `False` if not.
388
+ Additionally if the cookie's stored pin hash is wrong it will return
389
+ `None` so that appropriate action can be taken.
390
+ """
391
+ if self.pin is None:
392
+ return True
393
+ val = parse_cookie(environ).get(self.pin_cookie_name)
394
+ if not val or "|" not in val:
395
+ return False
396
+ ts, pin_hash = val.split("|", 1)
397
+ if not ts.isdigit():
398
+ return False
399
+ if pin_hash != hash_pin(self.pin):
400
+ return None
401
+ return (time.time() - PIN_TIME) < int(ts)
402
+
403
+ def _fail_pin_auth(self):
404
+ time.sleep(5.0 if self._failed_pin_auth > 5 else 0.5)
405
+ self._failed_pin_auth += 1
406
+
407
+ def pin_auth(self, request):
408
+ """Authenticates with the pin."""
409
+ exhausted = False
410
+ auth = False
411
+ trust = self.check_pin_trust(request.environ)
412
+
413
+ # If the trust return value is `None` it means that the cookie is
414
+ # set but the stored pin hash value is bad. This means that the
415
+ # pin was changed. In this case we count a bad auth and unset the
416
+ # cookie. This way it becomes harder to guess the cookie name
417
+ # instead of the pin as we still count up failures.
418
+ bad_cookie = False
419
+ if trust is None:
420
+ self._fail_pin_auth()
421
+ bad_cookie = True
422
+
423
+ # If we're trusted, we're authenticated.
424
+ elif trust:
425
+ auth = True
426
+
427
+ # If we failed too many times, then we're locked out.
428
+ elif self._failed_pin_auth > 10:
429
+ exhausted = True
430
+
431
+ # Otherwise go through pin based authentication
432
+ else:
433
+ entered_pin = request.args.get("pin")
434
+ if entered_pin.strip().replace("-", "") == self.pin.replace("-", ""):
435
+ self._failed_pin_auth = 0
436
+ auth = True
437
+ else:
438
+ self._fail_pin_auth()
439
+
440
+ rv = Response(
441
+ json.dumps({"auth": auth, "exhausted": exhausted}),
442
+ mimetype="application/json",
443
+ )
444
+ if auth:
445
+ rv.set_cookie(
446
+ self.pin_cookie_name,
447
+ "%s|%s" % (int(time.time()), hash_pin(self.pin)),
448
+ httponly=True,
449
+ )
450
+ elif bad_cookie:
451
+ rv.delete_cookie(self.pin_cookie_name)
452
+ return rv
453
+
454
+ def log_pin_request(self):
455
+ """Log the pin if needed."""
456
+ if self.pin_logging and self.pin is not None:
457
+ _log(
458
+ "info", " * To enable the debugger you need to enter the security pin:"
459
+ )
460
+ _log("info", " * Debugger pin code: %s" % self.pin)
461
+ return Response("")
462
+
463
+ def __call__(self, environ, start_response):
464
+ """Dispatch the requests."""
465
+ # important: don't ever access a function here that reads the incoming
466
+ # form data! Otherwise the application won't have access to that data
467
+ # any more!
468
+ request = Request(environ)
469
+ response = self.debug_application
470
+ if request.args.get("__debugger__") == "yes":
471
+ cmd = request.args.get("cmd")
472
+ arg = request.args.get("f")
473
+ secret = request.args.get("s")
474
+ traceback = self.tracebacks.get(request.args.get("tb", type=int))
475
+ frame = self.frames.get(request.args.get("frm", type=int))
476
+ if cmd == "resource" and arg:
477
+ response = self.get_resource(request, arg)
478
+ elif cmd == "paste" and traceback is not None and secret == self.secret:
479
+ response = self.paste_traceback(request, traceback)
480
+ elif cmd == "pinauth" and secret == self.secret:
481
+ response = self.pin_auth(request)
482
+ elif cmd == "printpin" and secret == self.secret:
483
+ response = self.log_pin_request()
484
+ elif (
485
+ self.evalex
486
+ and cmd is not None
487
+ and frame is not None
488
+ and self.secret == secret
489
+ and self.check_pin_trust(environ)
490
+ ):
491
+ response = self.execute_command(request, cmd, frame)
492
+ elif (
493
+ self.evalex
494
+ and self.console_path is not None
495
+ and request.path == self.console_path
496
+ ):
497
+ response = self.display_console(request)
498
+ return response(environ, start_response)
@@ -0,0 +1,218 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ werkzeug.debug.console
4
+ ~~~~~~~~~~~~~~~~~~~~~~
5
+
6
+ Interactive console support.
7
+
8
+ :copyright: 2007 Pallets
9
+ :license: BSD-3-Clause
10
+ """
11
+ import code
12
+ import sys
13
+ from types import CodeType
14
+
15
+ from ..local import Local
16
+ from ..utils import escape
17
+ from .repr import debug_repr
18
+ from .repr import dump
19
+ from .repr import helper
20
+
21
+
22
+ _local = Local()
23
+
24
+
25
+ class HTMLStringO(object):
26
+ """A StringO version that HTML escapes on write."""
27
+
28
+ def __init__(self):
29
+ self._buffer = []
30
+
31
+ def isatty(self):
32
+ return False
33
+
34
+ def close(self):
35
+ pass
36
+
37
+ def flush(self):
38
+ pass
39
+
40
+ def seek(self, n, mode=0):
41
+ pass
42
+
43
+ def readline(self):
44
+ if len(self._buffer) == 0:
45
+ return ""
46
+ ret = self._buffer[0]
47
+ del self._buffer[0]
48
+ return ret
49
+
50
+ def reset(self):
51
+ val = "".join(self._buffer)
52
+ del self._buffer[:]
53
+ return val
54
+
55
+ def _write(self, x):
56
+ if isinstance(x, bytes):
57
+ x = x.decode("utf-8", "replace")
58
+ self._buffer.append(x)
59
+
60
+ def write(self, x):
61
+ self._write(escape(x))
62
+
63
+ def writelines(self, x):
64
+ self._write(escape("".join(x)))
65
+
66
+
67
+ class ThreadedStream(object):
68
+ """Thread-local wrapper for sys.stdout for the interactive console."""
69
+
70
+ @staticmethod
71
+ def push():
72
+ if not isinstance(sys.stdout, ThreadedStream):
73
+ sys.stdout = ThreadedStream()
74
+ _local.stream = HTMLStringO()
75
+
76
+ @staticmethod
77
+ def fetch():
78
+ try:
79
+ stream = _local.stream
80
+ except AttributeError:
81
+ return ""
82
+ return stream.reset()
83
+
84
+ @staticmethod
85
+ def displayhook(obj):
86
+ try:
87
+ stream = _local.stream
88
+ except AttributeError:
89
+ return _displayhook(obj)
90
+ # stream._write bypasses escaping as debug_repr is
91
+ # already generating HTML for us.
92
+ if obj is not None:
93
+ _local._current_ipy.locals["_"] = obj
94
+ stream._write(debug_repr(obj))
95
+
96
+ def __setattr__(self, name, value):
97
+ raise AttributeError("read only attribute %s" % name)
98
+
99
+ def __dir__(self):
100
+ return dir(sys.__stdout__)
101
+
102
+ def __getattribute__(self, name):
103
+ if name == "__members__":
104
+ return dir(sys.__stdout__)
105
+ try:
106
+ stream = _local.stream
107
+ except AttributeError:
108
+ stream = sys.__stdout__
109
+ return getattr(stream, name)
110
+
111
+ def __repr__(self):
112
+ return repr(sys.__stdout__)
113
+
114
+
115
+ # add the threaded stream as display hook
116
+ _displayhook = sys.displayhook
117
+ sys.displayhook = ThreadedStream.displayhook
118
+
119
+
120
+ class _ConsoleLoader(object):
121
+ def __init__(self):
122
+ self._storage = {}
123
+
124
+ def register(self, code, source):
125
+ self._storage[id(code)] = source
126
+ # register code objects of wrapped functions too.
127
+ for var in code.co_consts:
128
+ if isinstance(var, CodeType):
129
+ self._storage[id(var)] = source
130
+
131
+ def get_source_by_code(self, code):
132
+ try:
133
+ return self._storage[id(code)]
134
+ except KeyError:
135
+ pass
136
+
137
+
138
+ def _wrap_compiler(console):
139
+ compile = console.compile
140
+
141
+ def func(source, filename, symbol):
142
+ code = compile(source, filename, symbol)
143
+ console.loader.register(code, source)
144
+ return code
145
+
146
+ console.compile = func
147
+
148
+
149
+ class _InteractiveConsole(code.InteractiveInterpreter):
150
+ def __init__(self, globals, locals):
151
+ _locals = dict(globals)
152
+ _locals.update(locals)
153
+ locals = _locals
154
+ locals["dump"] = dump
155
+ locals["help"] = helper
156
+ locals["__loader__"] = self.loader = _ConsoleLoader()
157
+ code.InteractiveInterpreter.__init__(self, locals)
158
+ self.more = False
159
+ self.buffer = []
160
+ _wrap_compiler(self)
161
+
162
+ def runsource(self, source):
163
+ source = source.rstrip() + "\n"
164
+ ThreadedStream.push()
165
+ prompt = "... " if self.more else ">>> "
166
+ try:
167
+ source_to_eval = "".join(self.buffer + [source])
168
+ if code.InteractiveInterpreter.runsource(
169
+ self, source_to_eval, "<debugger>", "single"
170
+ ):
171
+ self.more = True
172
+ self.buffer.append(source)
173
+ else:
174
+ self.more = False
175
+ del self.buffer[:]
176
+ finally:
177
+ output = ThreadedStream.fetch()
178
+ return prompt + escape(source) + output
179
+
180
+ def runcode(self, code):
181
+ try:
182
+ exec(code, self.locals)
183
+ except Exception:
184
+ self.showtraceback()
185
+
186
+ def showtraceback(self):
187
+ from .tbtools import get_current_traceback
188
+
189
+ tb = get_current_traceback(skip=1)
190
+ sys.stdout._write(tb.render_summary())
191
+
192
+ def showsyntaxerror(self, filename=None):
193
+ from .tbtools import get_current_traceback
194
+
195
+ tb = get_current_traceback(skip=4)
196
+ sys.stdout._write(tb.render_summary())
197
+
198
+ def write(self, data):
199
+ sys.stdout.write(data)
200
+
201
+
202
+ class Console(object):
203
+ """An interactive console."""
204
+
205
+ def __init__(self, globals=None, locals=None):
206
+ if locals is None:
207
+ locals = {}
208
+ if globals is None:
209
+ globals = {}
210
+ self._ipy = _InteractiveConsole(globals, locals)
211
+
212
+ def eval(self, code):
213
+ _local._current_ipy = self._ipy
214
+ old_sys_stdout = sys.stdout
215
+ try:
216
+ return self._ipy.runsource(code)
217
+ finally:
218
+ sys.stdout = old_sys_stdout