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.
- cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
- cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
- cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
- cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
- cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
- pythonagent/__init__.py +22 -0
- pythonagent/agent/__init__.py +219 -0
- pythonagent/agent/internal/__init__.py +1 -0
- pythonagent/agent/internal/agent.py +1651 -0
- pythonagent/agent/internal/framesinfo.py +152 -0
- pythonagent/agent/internal/heap_dump.py +39 -0
- pythonagent/agent/internal/intercept_module.py +70 -0
- pythonagent/agent/internal/logs.py +122 -0
- pythonagent/agent/internal/metadata/__init__.py +0 -0
- pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
- pythonagent/agent/internal/proc_compat.py +75 -0
- pythonagent/agent/internal/profile.py +47 -0
- pythonagent/agent/internal/provider.py +83 -0
- pythonagent/agent/internal/thread_dump.py +85 -0
- pythonagent/agent/internal/udp.py +245 -0
- pythonagent/agent/internal/udp_message.py +800 -0
- pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
- pythonagent/agent/probes/Instrumentation/find.py +243 -0
- pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
- pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
- pythonagent/agent/probes/Instrumentation/parser.py +129 -0
- pythonagent/agent/probes/__init__.py +219 -0
- pythonagent/agent/probes/base.py +303 -0
- pythonagent/agent/probes/cache/__init__.py +51 -0
- pythonagent/agent/probes/cache/redis.py +119 -0
- pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
- pythonagent/agent/probes/coroutines/__init__.py +1 -0
- pythonagent/agent/probes/coroutines/asyncio.py +63 -0
- pythonagent/agent/probes/elasticdb/__init__.py +7 -0
- pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
- pythonagent/agent/probes/frameworks/__init__.py +27 -0
- pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
- pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
- pythonagent/agent/probes/frameworks/aisess.py +151 -0
- pythonagent/agent/probes/frameworks/asgi.py +340 -0
- pythonagent/agent/probes/frameworks/bottle.py +27 -0
- pythonagent/agent/probes/frameworks/cherry.py +25 -0
- pythonagent/agent/probes/frameworks/django.py +128 -0
- pythonagent/agent/probes/frameworks/falcon.py +21 -0
- pythonagent/agent/probes/frameworks/fastapi.py +35 -0
- pythonagent/agent/probes/frameworks/flask.py +30 -0
- pythonagent/agent/probes/frameworks/pyramid.py +56 -0
- pythonagent/agent/probes/frameworks/test.py +108 -0
- pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
- pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
- pythonagent/agent/probes/frameworks/wsgi.py +353 -0
- pythonagent/agent/probes/grpc/__init__.py +76 -0
- pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
- pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
- pythonagent/agent/probes/havoc/__init__.py +0 -0
- pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
- pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
- pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
- pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
- pythonagent/agent/probes/http/__init__.py +49 -0
- pythonagent/agent/probes/http/aiohttp_client.py +59 -0
- pythonagent/agent/probes/http/boto.py +12 -0
- pythonagent/agent/probes/http/httplib.py +110 -0
- pythonagent/agent/probes/http/httpx_client.py +116 -0
- pythonagent/agent/probes/http/requests.py +15 -0
- pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
- pythonagent/agent/probes/http/urllib3.py +16 -0
- pythonagent/agent/probes/langchain/__init__.py +21 -0
- pythonagent/agent/probes/langchain/base_tool.py +95 -0
- pythonagent/agent/probes/langchain/langchain_community.py +136 -0
- pythonagent/agent/probes/langchain/langchain_core.py +32 -0
- pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
- pythonagent/agent/probes/logging/__init__.py +106 -0
- pythonagent/agent/probes/message_brokers/__init__.py +4 -0
- pythonagent/agent/probes/message_brokers/pika.py +126 -0
- pythonagent/agent/probes/mongodb/__init__.py +6 -0
- pythonagent/agent/probes/mongodb/pymongo.py +286 -0
- pythonagent/agent/probes/openai/__init__.py +3 -0
- pythonagent/agent/probes/openai/openai.py +797 -0
- pythonagent/agent/probes/span.py +101 -0
- pythonagent/agent/probes/sql/__init__.py +13 -0
- pythonagent/agent/probes/sql/botocores3.py +51 -0
- pythonagent/agent/probes/sql/dbapi.py +285 -0
- pythonagent/agent/probes/sql/dynamodb.py +90 -0
- pythonagent/agent/probes/sql/mysql_connector.py +24 -0
- pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
- pythonagent/agent/probes/sql/mysqldb.py +43 -0
- pythonagent/agent/probes/sql/psycopg2.py +174 -0
- pythonagent/agent/probes/sql/pymysql.py +25 -0
- pythonagent/bootstrap/__init__.py +0 -0
- pythonagent/bootstrap/cav_gunicorn.py +26 -0
- pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
- pythonagent/bootstrap/run.py +47 -0
- pythonagent/bootstrap/sitecustomize.py +287 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
- pythonagent/config.py +279 -0
- pythonagent/find.py +72 -0
- pythonagent/find_mod_cls_name.py +54 -0
- pythonagent/lang.py +131 -0
- pythonagent/lib.py +91 -0
- pythonagent/main/__init__.py +0 -0
- pythonagent/main/pytrace/__init__.py +79 -0
- pythonagent/main/pytrace/commands/__init__.py +0 -0
- pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
- pythonagent/main/pytrace/commands/run.py +401 -0
- pythonagent/main/pytrace/pytrace.py +133 -0
- pythonagent/main/wsgi.py +6 -0
- pythonagent/main.py +27 -0
- pythonagent/run.py +46 -0
- pythonagent/sqins.py +8 -0
- pythonagent/test.py +73 -0
- pythonagent/utils.py +168 -0
- pythonagent/vendor/__init__.py +0 -0
- pythonagent/vendor/pympler/__init__.py +1 -0
- pythonagent/vendor/pympler/asizeof.py +2810 -0
- pythonagent/vendor/pympler/charts.py +62 -0
- pythonagent/vendor/pympler/classtracker.py +590 -0
- pythonagent/vendor/pympler/classtracker_stats.py +780 -0
- pythonagent/vendor/pympler/garbagegraph.py +80 -0
- pythonagent/vendor/pympler/mprofile.py +97 -0
- pythonagent/vendor/pympler/muppy.py +275 -0
- pythonagent/vendor/pympler/panels.py +115 -0
- pythonagent/vendor/pympler/process.py +238 -0
- pythonagent/vendor/pympler/py.typed +0 -0
- pythonagent/vendor/pympler/refbrowser.py +451 -0
- pythonagent/vendor/pympler/refgraph.py +350 -0
- pythonagent/vendor/pympler/summary.py +321 -0
- pythonagent/vendor/pympler/tracker.py +267 -0
- pythonagent/vendor/pympler/util/__init__.py +0 -0
- pythonagent/vendor/pympler/util/bottle.py +3809 -0
- pythonagent/vendor/pympler/util/compat.py +23 -0
- pythonagent/vendor/pympler/util/stringutils.py +77 -0
- pythonagent/vendor/pympler/web.py +346 -0
- pythonagent/vendor/werkzeug/__init__.py +20 -0
- pythonagent/vendor/werkzeug/_compat.py +228 -0
- pythonagent/vendor/werkzeug/_internal.py +473 -0
- pythonagent/vendor/werkzeug/_reloader.py +341 -0
- pythonagent/vendor/werkzeug/datastructures.py +3120 -0
- pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
- pythonagent/vendor/werkzeug/debug/console.py +218 -0
- pythonagent/vendor/werkzeug/debug/repr.py +297 -0
- pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
- pythonagent/vendor/werkzeug/exceptions.py +829 -0
- pythonagent/vendor/werkzeug/filesystem.py +64 -0
- pythonagent/vendor/werkzeug/formparser.py +584 -0
- pythonagent/vendor/werkzeug/http.py +1307 -0
- pythonagent/vendor/werkzeug/local.py +420 -0
- pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
- pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
- pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
- pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
- pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
- pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
- pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
- pythonagent/vendor/werkzeug/posixemulation.py +117 -0
- pythonagent/vendor/werkzeug/routing.py +2210 -0
- pythonagent/vendor/werkzeug/security.py +249 -0
- pythonagent/vendor/werkzeug/serving.py +1117 -0
- pythonagent/vendor/werkzeug/test.py +1123 -0
- pythonagent/vendor/werkzeug/testapp.py +241 -0
- pythonagent/vendor/werkzeug/urls.py +1138 -0
- pythonagent/vendor/werkzeug/useragents.py +202 -0
- pythonagent/vendor/werkzeug/utils.py +778 -0
- pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
- pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
- pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
- pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
- pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
- pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
- pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
- pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
- pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
- pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
- pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
- pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
- 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
|