|  | @@ -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))
 |