|
@@ -1,4 +1,16 @@
|
1
|
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+from xml.etree import ElementTree
|
|
3
|
+
|
|
4
|
+import os
|
|
5
|
+import sys
|
|
6
|
+import threading
|
|
7
|
+import time
|
|
8
|
+from datetime import datetime
|
|
9
|
+
|
|
10
|
+import yaml
|
|
11
|
+from wsgidav import util
|
|
12
|
+from wsgidav import compat
|
|
13
|
+from wsgidav.middleware import BaseMiddleware
|
2
|
14
|
|
3
|
15
|
|
4
|
16
|
def transform_to_display(string: str) -> str:
|
|
@@ -47,3 +59,212 @@ def transform_to_bdd(string: str) -> str:
|
47
|
59
|
string = string.replace(key, value)
|
48
|
60
|
|
49
|
61
|
return string
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+class TracimWsgiDavDebugFilter(BaseMiddleware):
|
|
65
|
+ """
|
|
66
|
+ COPY PASTE OF wsgidav.debug_filter.WsgiDavDebugFilter
|
|
67
|
+ WITH ADD OF DUMP RESPONSE & REQUEST
|
|
68
|
+ """
|
|
69
|
+ def __init__(self, application, config):
|
|
70
|
+ self._application = application
|
|
71
|
+ self._config = config
|
|
72
|
+ # self.out = sys.stderr
|
|
73
|
+ self.out = sys.stdout
|
|
74
|
+ self.passedLitmus = {}
|
|
75
|
+ # These methods boost verbose=2 to verbose=3
|
|
76
|
+ self.debug_methods = config.get("debug_methods", [])
|
|
77
|
+ # Litmus tests containing these string boost verbose=2 to verbose=3
|
|
78
|
+ self.debug_litmus = config.get("debug_litmus", [])
|
|
79
|
+ # Exit server, as soon as this litmus test has finished
|
|
80
|
+ self.break_after_litmus = [
|
|
81
|
+ # "locks: 15",
|
|
82
|
+ ]
|
|
83
|
+
|
|
84
|
+ if self._config.get('dump_requests'):
|
|
85
|
+ # Monkey patching
|
|
86
|
+ old_parseXmlBody = util.parseXmlBody
|
|
87
|
+ def new_parseXmlBody(environ, allowEmpty=False):
|
|
88
|
+ xml = old_parseXmlBody(environ, allowEmpty)
|
|
89
|
+ self._dump_request(environ, xml)
|
|
90
|
+ return xml
|
|
91
|
+ util.parseXmlBody = new_parseXmlBody
|
|
92
|
+
|
|
93
|
+ def __call__(self, environ, start_response):
|
|
94
|
+ """"""
|
|
95
|
+ # srvcfg = environ["wsgidav.config"]
|
|
96
|
+ verbose = self._config.get("verbose", 2)
|
|
97
|
+
|
|
98
|
+ method = environ["REQUEST_METHOD"]
|
|
99
|
+
|
|
100
|
+ debugBreak = False
|
|
101
|
+ dumpRequest = False
|
|
102
|
+ dumpResponse = False
|
|
103
|
+
|
|
104
|
+ if verbose >= 3 or self._config.get("dump_requests"):
|
|
105
|
+ dumpRequest = dumpResponse = True
|
|
106
|
+
|
|
107
|
+ # Process URL commands
|
|
108
|
+ if "dump_storage" in environ.get("QUERY_STRING"):
|
|
109
|
+ dav = environ.get("wsgidav.provider")
|
|
110
|
+ if dav.lockManager:
|
|
111
|
+ dav.lockManager._dump()
|
|
112
|
+ if dav.propManager:
|
|
113
|
+ dav.propManager._dump()
|
|
114
|
+
|
|
115
|
+ # Turn on max. debugging for selected litmus tests
|
|
116
|
+ litmusTag = environ.get("HTTP_X_LITMUS",
|
|
117
|
+ environ.get("HTTP_X_LITMUS_SECOND"))
|
|
118
|
+ if litmusTag and verbose >= 2:
|
|
119
|
+ print("----\nRunning litmus test '%s'..." % litmusTag,
|
|
120
|
+ file=self.out)
|
|
121
|
+ for litmusSubstring in self.debug_litmus:
|
|
122
|
+ if litmusSubstring in litmusTag:
|
|
123
|
+ verbose = 3
|
|
124
|
+ debugBreak = True
|
|
125
|
+ dumpRequest = True
|
|
126
|
+ dumpResponse = True
|
|
127
|
+ break
|
|
128
|
+ for litmusSubstring in self.break_after_litmus:
|
|
129
|
+ if litmusSubstring in self.passedLitmus and litmusSubstring not in litmusTag:
|
|
130
|
+ print(" *** break after litmus %s" % litmusTag,
|
|
131
|
+ file=self.out)
|
|
132
|
+ sys.exit(-1)
|
|
133
|
+ if litmusSubstring in litmusTag:
|
|
134
|
+ self.passedLitmus[litmusSubstring] = True
|
|
135
|
+
|
|
136
|
+ # Turn on max. debugging for selected request methods
|
|
137
|
+ if verbose >= 2 and method in self.debug_methods:
|
|
138
|
+ verbose = 3
|
|
139
|
+ debugBreak = True
|
|
140
|
+ dumpRequest = True
|
|
141
|
+ dumpResponse = True
|
|
142
|
+
|
|
143
|
+ # Set debug options to environment
|
|
144
|
+ environ["wsgidav.verbose"] = verbose
|
|
145
|
+ # environ["wsgidav.debug_methods"] = self.debug_methods
|
|
146
|
+ environ["wsgidav.debug_break"] = debugBreak
|
|
147
|
+ environ["wsgidav.dump_request_body"] = dumpRequest
|
|
148
|
+ environ["wsgidav.dump_response_body"] = dumpResponse
|
|
149
|
+
|
|
150
|
+ # Dump request headers
|
|
151
|
+ if dumpRequest:
|
|
152
|
+ print("<%s> --- %s Request ---" % (
|
|
153
|
+ threading.currentThread().ident, method), file=self.out)
|
|
154
|
+ for k, v in environ.items():
|
|
155
|
+ if k == k.upper():
|
|
156
|
+ print("%20s: '%s'" % (k, v), file=self.out)
|
|
157
|
+ print("\n", file=self.out)
|
|
158
|
+
|
|
159
|
+ # Intercept start_response
|
|
160
|
+ #
|
|
161
|
+ sub_app_start_response = util.SubAppStartResponse()
|
|
162
|
+
|
|
163
|
+ nbytes = 0
|
|
164
|
+ first_yield = True
|
|
165
|
+ app_iter = self._application(environ, sub_app_start_response)
|
|
166
|
+
|
|
167
|
+ for v in app_iter:
|
|
168
|
+ # Start response (the first time)
|
|
169
|
+ if first_yield:
|
|
170
|
+ # Success!
|
|
171
|
+ start_response(sub_app_start_response.status,
|
|
172
|
+ sub_app_start_response.response_headers,
|
|
173
|
+ sub_app_start_response.exc_info)
|
|
174
|
+
|
|
175
|
+ # Dump response headers
|
|
176
|
+ if first_yield and dumpResponse:
|
|
177
|
+ print("<%s> --- %s Response(%s): ---" % (
|
|
178
|
+ threading.currentThread().ident,
|
|
179
|
+ method,
|
|
180
|
+ sub_app_start_response.status),
|
|
181
|
+ file=self.out)
|
|
182
|
+ headersdict = dict(sub_app_start_response.response_headers)
|
|
183
|
+ for envitem in headersdict.keys():
|
|
184
|
+ print("%s: %s" % (envitem, repr(headersdict[envitem])),
|
|
185
|
+ file=self.out)
|
|
186
|
+ print("", file=self.out)
|
|
187
|
+
|
|
188
|
+ # Check, if response is a binary string, otherwise we probably have
|
|
189
|
+ # calculated a wrong content-length
|
|
190
|
+ assert compat.is_bytes(v), v
|
|
191
|
+
|
|
192
|
+ # Dump response body
|
|
193
|
+ drb = environ.get("wsgidav.dump_response_body")
|
|
194
|
+ if compat.is_basestring(drb):
|
|
195
|
+ # Middleware provided a formatted body representation
|
|
196
|
+ print(drb, file=self.out)
|
|
197
|
+ elif drb is True:
|
|
198
|
+ # Else dump what we get, (except for long GET responses)
|
|
199
|
+ if method == "GET":
|
|
200
|
+ if first_yield:
|
|
201
|
+ print(v[:50], "...", file=self.out)
|
|
202
|
+ elif len(v) > 0:
|
|
203
|
+ print(v, file=self.out)
|
|
204
|
+
|
|
205
|
+ if dumpResponse:
|
|
206
|
+ self._dump_response(sub_app_start_response, drb)
|
|
207
|
+
|
|
208
|
+ drb = environ["wsgidav.dump_response_body"] = None
|
|
209
|
+
|
|
210
|
+ nbytes += len(v)
|
|
211
|
+ first_yield = False
|
|
212
|
+ yield v
|
|
213
|
+ if hasattr(app_iter, "close"):
|
|
214
|
+ app_iter.close()
|
|
215
|
+
|
|
216
|
+ # Start response (if it hasn't been done yet)
|
|
217
|
+ if first_yield:
|
|
218
|
+ # Success!
|
|
219
|
+ start_response(sub_app_start_response.status,
|
|
220
|
+ sub_app_start_response.response_headers,
|
|
221
|
+ sub_app_start_response.exc_info)
|
|
222
|
+
|
|
223
|
+ if dumpResponse:
|
|
224
|
+ print("\n<%s> --- End of %s Response (%i bytes) ---" % (
|
|
225
|
+ threading.currentThread().ident, method, nbytes), file=self.out)
|
|
226
|
+ return
|
|
227
|
+
|
|
228
|
+ def _dump_response(self, sub_app_start_response, drb):
|
|
229
|
+ dump_to_path = self._config.get(
|
|
230
|
+ 'dump_requests_path',
|
|
231
|
+ '/tmp/wsgidav_dumps',
|
|
232
|
+ )
|
|
233
|
+ os.makedirs(dump_to_path, exist_ok=True)
|
|
234
|
+ dump_file = '{0}/{1}_RESPONSE_{2}.yml'.format(
|
|
235
|
+ dump_to_path,
|
|
236
|
+ '{0}_{1}'.format(
|
|
237
|
+ datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S'),
|
|
238
|
+ int(round(time.time() * 1000)),
|
|
239
|
+ ),
|
|
240
|
+ sub_app_start_response.status[0:3],
|
|
241
|
+ )
|
|
242
|
+ with open(dump_file, 'w+') as f:
|
|
243
|
+ dump_content = dict()
|
|
244
|
+ if isinstance(drb, str):
|
|
245
|
+ dump_content['content'] = drb.replace('PROPFIND XML response body:\n', '')
|
|
246
|
+
|
|
247
|
+ f.write(yaml.dump(dump_content, default_flow_style=False))
|
|
248
|
+
|
|
249
|
+ def _dump_request(self, environ, xml):
|
|
250
|
+ dump_to_path = self._config.get(
|
|
251
|
+ 'dump_requests_path',
|
|
252
|
+ '/tmp/wsgidav_dumps',
|
|
253
|
+ )
|
|
254
|
+ os.makedirs(dump_to_path, exist_ok=True)
|
|
255
|
+ dump_file = '{0}/{1}_REQUEST_{2}.yml'.format(
|
|
256
|
+ dump_to_path,
|
|
257
|
+ '{0}_{1}'.format(
|
|
258
|
+ datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S'),
|
|
259
|
+ int(round(time.time() * 1000)),
|
|
260
|
+ ),
|
|
261
|
+ environ['REQUEST_METHOD'],
|
|
262
|
+ )
|
|
263
|
+ with open(dump_file, 'w+') as f:
|
|
264
|
+ dump_content = dict()
|
|
265
|
+ dump_content['path'] = environ['PATH_INFO']
|
|
266
|
+ dump_content['Authorization'] = environ['HTTP_AUTHORIZATION']
|
|
267
|
+ if xml:
|
|
268
|
+ dump_content['content'] = ElementTree.tostring(xml, 'utf-8')
|
|
269
|
+
|
|
270
|
+ f.write(yaml.dump(dump_content, default_flow_style=False))
|