浏览代码

WebDav HTTP dump of requests

Bastien Sevajol (Algoo) 8 年前
父节点
当前提交
5d374ca742
共有 3 个文件被更改,包括 117 次插入31 次删除
  1. 1 1
      install/requirements.txt
  2. 113 29
      tracim/tracim/lib/daemons.py
  3. 3 1
      tracim/tracim/lib/webdav/sql_resources.py

+ 1 - 1
install/requirements.txt 查看文件

62
 -e git+https://github.com/algoo/wsgidav.git@py3#egg=wsgidav
62
 -e git+https://github.com/algoo/wsgidav.git@py3#egg=wsgidav
63
 zope.interface==4.1.3
63
 zope.interface==4.1.3
64
 zope.sqlalchemy==0.7.6
64
 zope.sqlalchemy==0.7.6
65
-
65
+PyYAML

+ 113 - 29
tracim/tracim/lib/daemons.py 查看文件

1
 import threading
1
 import threading
2
 from configparser import DuplicateSectionError
2
 from configparser import DuplicateSectionError
3
+from datetime import datetime
3
 from wsgiref.simple_server import make_server
4
 from wsgiref.simple_server import make_server
4
 import signal
5
 import signal
5
 
6
 
6
 import collections
7
 import collections
7
-import transaction
8
+import time
9
+
10
+import io
11
+import yaml
8
 
12
 
9
 from radicale import Application as RadicaleApplication
13
 from radicale import Application as RadicaleApplication
10
 from radicale import HTTPServer as BaseRadicaleHTTPServer
14
 from radicale import HTTPServer as BaseRadicaleHTTPServer
254
 from inspect import isfunction
258
 from inspect import isfunction
255
 import traceback
259
 import traceback
256
 
260
 
261
+from wsgidav.server.cherrypy import wsgiserver
262
+from wsgidav.server.cherrypy.wsgiserver.wsgiserver3 import \
263
+    HTTPConnection as BaseHTTPConnection
264
+from wsgidav.server.cherrypy.wsgiserver.wsgiserver3 import \
265
+    HTTPRequest as BaseHTTPRequest
266
+
257
 DEFAULT_CONFIG_FILE = "wsgidav.conf"
267
 DEFAULT_CONFIG_FILE = "wsgidav.conf"
258
 PYTHON_VERSION = "%s.%s.%s" % (sys.version_info[0], sys.version_info[1], sys.version_info[2])
268
 PYTHON_VERSION = "%s.%s.%s" % (sys.version_info[0], sys.version_info[1], sys.version_info[2])
259
 
269
 
260
 
270
 
271
+class HTTPRequest(BaseHTTPRequest):
272
+    """
273
+    Exeprimental override of HTTPRequest designed to permit
274
+    dump of HTTP requests
275
+    """
276
+    def parse_request(self, can_dump: bool=True):
277
+        super().parse_request()
278
+        dump = self.server.wsgi_app.config.get('dump_requests', False)
279
+        if self.ready and dump and can_dump:
280
+            dump_to_path = self.server.wsgi_app.config.get(
281
+                'dump_requests_path',
282
+                '/tmp/wsgidav_dumps',
283
+            )
284
+            self.dump(dump_to_path)
285
+
286
+    def dump(self, dump_to_path: str):
287
+        if self.ready:
288
+            os.makedirs(dump_to_path, exist_ok=True)
289
+            dump_file = '{0}/{1}_{2}.yml'.format(
290
+                dump_to_path,
291
+                '{0}_{1}'.format(
292
+                    datetime.utcnow().strftime('%Y-%m-%d_%H-%I-%S'),
293
+                    int(round(time.time() * 1000)),
294
+                ),
295
+                self.method.decode('utf-8'),
296
+            )
297
+            with open(dump_file, 'w+') as f:
298
+                dump_content = dict()
299
+                dump_content['path'] = self.path.decode('ISO-8859-1')
300
+                str_headers = dict(
301
+                    (k.decode('utf8'), v.decode('ISO-8859-1')) for k, v in
302
+                    self.inheaders.items()
303
+                )
304
+                dump_content['headers'] = str_headers
305
+                dump_content['content'] = self.conn.read_content\
306
+                    .decode('ISO-8859-1')
307
+
308
+                f.write(yaml.dump(dump_content, default_flow_style=False))
309
+
310
+
311
+class HTTPConnection(BaseHTTPConnection):
312
+    """
313
+    Exeprimental override of HTTPConnection designed to permit
314
+    dump of HTTP requests
315
+    """
316
+    RequestHandlerClass = HTTPRequest
317
+
318
+    def __init__(self, server, sock, *args, **kwargs):
319
+        super().__init__(server, sock, *args, **kwargs)
320
+
321
+        if self.server.wsgi_app.config.get('dump_requests', False):
322
+            # We use HTTPRequest to parse headers, path, etc ...
323
+            req = self.RequestHandlerClass(self.server, self)
324
+            req.parse_request(can_dump=False)
325
+
326
+            # And we read request content
327
+            content_length = int(req.inheaders.get(b'Content-Length', b'0'))
328
+            content = req.rfile.read(content_length)
329
+
330
+            # We are now able to rebuild HTTP request
331
+            full_content = \
332
+                req.method + \
333
+                b' ' + \
334
+                req.uri + \
335
+                b' ' + \
336
+                req.request_protocol +\
337
+                b'\r\n'
338
+            for header_name, header_value in req.inheaders.items():
339
+                full_content += header_name + b': ' + header_value + b'\r\n'
340
+
341
+            full_content += b'\r\n' + content
342
+
343
+            # To give it again at HTTPConnection
344
+            bf = io.BufferedReader(io.BytesIO(full_content), self.rbufsize)
345
+
346
+            self.rfile = bf
347
+            # We will be able to dump request content with self.read_content
348
+            self.read_content = content
349
+
350
+
351
+class CherryPyWSGIServer(wsgiserver.CherryPyWSGIServer):
352
+    ConnectionClass = HTTPConnection
353
+
354
+
261
 class WsgiDavDaemon(Daemon):
355
 class WsgiDavDaemon(Daemon):
262
 
356
 
263
     def __init__(self, *args, **kwargs):
357
     def __init__(self, *args, **kwargs):
334
         app = WsgiDAVApp(self.config)
428
         app = WsgiDAVApp(self.config)
335
 
429
 
336
         # Try running WsgiDAV inside the following external servers:
430
         # Try running WsgiDAV inside the following external servers:
337
-        self._runCherryPy(app, self.config, "cherrypy-bundled")
338
-
339
-    def _runCherryPy(self, app, config, mode):
340
-        """Run WsgiDAV using cherrypy.wsgiserver, if CherryPy is installed."""
341
-        assert mode in ("cherrypy", "cherrypy-bundled")
342
-
343
-        try:
344
-            from wsgidav.server.cherrypy import wsgiserver
431
+        self._runCherryPy(app, self.config)
345
 
432
 
346
-            version = "WsgiDAV/%s %s Python/%s" % (
347
-                __version__,
348
-                wsgiserver.CherryPyWSGIServer.version,
349
-                PYTHON_VERSION)
433
+    def _runCherryPy(self, app, config):
434
+        version = "WsgiDAV/%s %s Python/%s" % (
435
+            __version__,
436
+            wsgiserver.CherryPyWSGIServer.version,
437
+            PYTHON_VERSION
438
+        )
350
 
439
 
351
-            wsgiserver.CherryPyWSGIServer.version = version
440
+        wsgiserver.CherryPyWSGIServer.version = version
352
 
441
 
353
-            protocol = "http"
442
+        protocol = "http"
354
 
443
 
355
-            if config["verbose"] >= 1:
356
-                print("Running %s" % version)
357
-                print("Listening on %s://%s:%s ..." % (protocol, config["host"], config["port"]))
358
-            self._server = wsgiserver.CherryPyWSGIServer(
359
-                (config["host"], config["port"]),
360
-                app,
361
-                server_name=version,
362
-            )
444
+        if config["verbose"] >= 1:
445
+            print("Running %s" % version)
446
+            print("Listening on %s://%s:%s ..." % (protocol, config["host"], config["port"]))
447
+        self._server = CherryPyWSGIServer(
448
+            (config["host"], config["port"]),
449
+            app,
450
+            server_name=version,
451
+        )
363
 
452
 
364
-            self._server.start()
365
-        except ImportError as e:
366
-            if config["verbose"] >= 1:
367
-                print("Could not import wsgiserver.CherryPyWSGIServer.")
368
-            return False
369
-        return True
453
+        self._server.start()
370
 
454
 
371
     def stop(self):
455
     def stop(self):
372
         self._server.stop()
456
         self._server.stop()

+ 3 - 1
tracim/tracim/lib/webdav/sql_resources.py 查看文件

72
         # thus we want to rename a file from 'file.txt' to 'file - deleted... .txt' and not 'file.txt - deleted...'
72
         # thus we want to rename a file from 'file.txt' to 'file - deleted... .txt' and not 'file.txt - deleted...'
73
         is_file_name = self.content.label == ''
73
         is_file_name = self.content.label == ''
74
         if is_file_name:
74
         if is_file_name:
75
-            extension = re.search(r'(\.[^.]+)$', new_name).group(0)
75
+            search = re.search(r'(\.[^.]+)$', new_name)
76
+            if search:
77
+                extension = search.group(0)
76
             new_name = re.sub(r'(\.[^.]+)$', '', new_name)
78
             new_name = re.sub(r'(\.[^.]+)$', '', new_name)
77
 
79
 
78
         if self._type in [ActionDescription.ARCHIVING, ActionDescription.DELETION]:
80
         if self._type in [ActionDescription.ARCHIVING, ActionDescription.DELETION]: