Commits

Robert Brewer committed 43d9a38

Final fix for #915 (Add "debug=False" arg to builtin Tools).

  • Participants
  • Parent commits edbb108

Comments (0)

Files changed (7)

cherrypy/_cptools.py

         self._name = name
         self._priority = priority
     
-    def callable(self):
+    def callable(self, debug=False):
         innerfunc = cherrypy.serving.request.handler
         def wrap(*args, **kwargs):
             return self.newhandler(innerfunc, *args, **kwargs)

cherrypy/lib/auth.py

         request.login = False
     return False
 
-def basic_auth(realm, users, encrypt=None):
+def basic_auth(realm, users, encrypt=None, debug=False):
     """If auth fails, raise 401 with a basic authentication header.
     
     realm: a string containing the authentication realm.
              if None it defaults to a md5 encryption.
     """
     if check_auth(users, encrypt):
+        if debug:
+            cherrypy.log('Auth successful', 'TOOLS.BASIC_AUTH')
         return
     
     # inform the user-agent this path is protected
     cherrypy.serving.response.headers['www-authenticate'] = httpauth.basicAuth(realm)
     
-    raise cherrypy.HTTPError(401, "You are not authorized to access that resource") 
+    raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
 
-def digest_auth(realm, users):
+def digest_auth(realm, users, debug=False):
     """If auth fails, raise 401 with a digest authentication header.
     
     realm: a string containing the authentication realm.
     users: a dict of the form: {username: password} or a callable returning a dict.
     """
     if check_auth(users, realm=realm):
+        if debug:
+            cherrypy.log('Auth successful', 'TOOLS.DIGEST_AUTH')
         return
     
     # inform the user-agent this path is protected
     cherrypy.serving.response.headers['www-authenticate'] = httpauth.digestAuth(realm)
     
-    raise cherrypy.HTTPError(401, "You are not authorized to access that resource") 
- 
+    raise cherrypy.HTTPError(401, "You are not authorized to access that resource")
+

cherrypy/lib/auth_basic.py

     return checkpassword
 
 
-def basic_auth(realm, checkpassword):
+def basic_auth(realm, checkpassword, debug=False):
     """basic_auth is a CherryPy tool which hooks at before_handler to perform
     HTTP Basic Access Authentication, as specified in RFC 2617.
 
                 username_password = base64.decodestring(params)
                 username, password = username_password.split(':', 1)
                 if checkpassword(realm, username, password):
+                    if debug:
+                        cherrypy.log('Auth succeeded', 'TOOLS.AUTH_BASIC')
                     request.login = username
                     return # successful authentication
         except (ValueError, binascii.Error): # split() error, base64.decodestring() error

cherrypy/lib/auth_digest.py

 
 
 def TRACE(msg):
-    cherrypy.log(msg, context='auth_digest')
+    cherrypy.log(msg, context='TOOLS.AUTH_DIGEST')
 
 # Three helper functions for users of the tool, providing three variants
 # of get_ha1() functions for three different kinds of credential stores.

cherrypy/lib/cptools.py

             request.remote.ip = xff
 
 
-def ignore_headers(headers=('Range',)):
+def ignore_headers(headers=('Range',), debug=False):
     """Delete request headers whose field names are included in 'headers'.
     
     This is a useful tool for working behind certain HTTP servers;
     request = cherrypy.serving.request
     for name in headers:
         if name in request.headers:
+            if debug:
+                cherrypy.log('Ignoring request header %r' % name,
+                             'TOOLS.IGNORE_HEADERS')
             del request.headers[name]
 
 
 def response_headers(headers=None, debug=False):
     """Set headers on the response."""
+    if debug:
+        cherrypy.log('Setting response headers: %s' % repr(headers),
+                     'TOOLS.RESPONSE_HEADERS')
     for name, value in (headers or []):
         cherrypy.serving.response.headers[name] = value
 response_headers.failsafe = True
 
 
 def referer(pattern, accept=True, accept_missing=False, error=403,
-            message='Forbidden Referer header.'):
+            message='Forbidden Referer header.', debug=False):
     """Raise HTTPError if Referer header does/does not match the given pattern.
     
     pattern: a regular expression pattern to test against the Referer.
     message: a string to include in the response body on failure.
     """
     try:
-        match = bool(re.match(pattern, cherrypy.serving.request.headers['Referer']))
+        ref = cherrypy.serving.request.headers['Referer']
+        match = bool(re.match(pattern, ref))
+        if debug:
+            cherrypy.log('Referer %r matches %r' % (ref, pattern),
+                         'TOOLS.REFERER')
         if accept == match:
             return
     except KeyError:
+        if debug:
+            cherrypy.log('No Referer header', 'TOOLS.REFERER')
         if accept_missing:
             return
     
     else:
         raise cherrypy.HTTPRedirect(url)
 
-def trailing_slash(missing=True, extra=False, status=None):
+def trailing_slash(missing=True, extra=False, status=None, debug=False):
     """Redirect if path_info has (missing|extra) trailing slash."""
     request = cherrypy.serving.request
     pi = request.path_info
     
+    if debug:
+        cherrypy.log('is_index: %r, missing: %r, extra: %r, path_info: %r' %
+                     (request.is_index, missing, extra, path_info),
+                     'TOOLS.TRAILING_SLASH')
     if request.is_index is True:
         if missing:
             if not pi.endswith('/'):
                 new_url = cherrypy.url(pi[:-1], request.query_string)
                 raise cherrypy.HTTPRedirect(new_url, status=status or 301)
 
-def flatten():
+def flatten(debug=False):
     """Wrap response.body in a generator that recursively iterates over body.
     
     This allows cherrypy.response.body to consist of 'nested generators';
     """
     import types
     def flattener(input):
+        numchunks = 0
         for x in input:
             if not isinstance(x, types.GeneratorType):
+                numchunks += 1
                 yield x
             else:
                 for y in flattener(x):
-                    yield y 
+                    numchunks += 1
+                    yield y
+        if debug:
+            cherrypy.log('Flattened %d chunks' % numchunks, 'TOOLS.FLATTEN')
     response = cherrypy.serving.response
     response.body = flattener(response.body)
 
 
-def accept(media=None):
+def accept(media=None, debug=False):
     """Return the client's preferred media-type (from the given Content-Types).
     
     If 'media' is None (the default), no test will be performed.
     ranges = request.headers.elements('Accept')
     if not ranges:
         # Any media type is acceptable.
+        if debug:
+            cherrypy.log('No Accept header elements', 'TOOLS.ACCEPT')
         return media[0]
     else:
         # Note that 'ranges' is sorted in order of preference
             if element.qvalue > 0:
                 if element.value == "*/*":
                     # Matches any type or subtype
+                    if debug:
+                        cherrypy.log('Match due to */*', 'TOOLS.ACCEPT')
                     return media[0]
                 elif element.value.endswith("/*"):
                     # Matches any subtype
                     mtype = element.value[:-1]  # Keep the slash
                     for m in media:
                         if m.startswith(mtype):
+                            if debug:
+                                cherrypy.log('Match due to %s' % element.value,
+                                             'TOOLS.ACCEPT')
                             return m
                 else:
                     # Matches exact value
                     if element.value in media:
+                        if debug:
+                            cherrypy.log('Match due to %s' % element.value,
+                                         'TOOLS.ACCEPT')
                         return element.value
     
     # No suitable media-range found.
         return _httputil.HeaderMap.has_key(self, key)
 
 
-def autovary(ignore=None):
+def autovary(ignore=None, debug=False):
     """Auto-populate the Vary response header based on request.header access."""
     request = cherrypy.serving.request
     
     def set_response_header():
         resp_h = cherrypy.serving.response.headers
         v = set([e.value for e in resp_h.elements('Vary')])
+        if debug:
+            cherrypy.log('Accessed headers: %s' % request.headers.accessed_headers,
+                         'TOOLS.AUTOVARY')
         v = v.union(request.headers.accessed_headers)
         v = v.difference(ignore)
         v = list(v)
         v.sort()
         resp_h['Vary'] = ', '.join(v)
-    request.hooks.attach(
-        'before_finalize', set_response_header, 95)
+    request.hooks.attach('before_finalize', set_response_header, 95)
 
-
-

cherrypy/lib/sessions.py

     True if the application called session.regenerate(). This is not set by
     internal calls to regenerate the session id."""
     
+    debug=False
+    
     def __init__(self, id=None, **kwargs):
         self.id_observers = []
         self._data = {}
         self.originalid = id
         self.missing = False
         if id is None:
+            if self.debug:
+                cherrypy.log('No id given; making a new one', 'TOOLS.SESSIONS')
             self._regenerate()
         else:
             self.id = id
             if not self._exists():
+                if self.debug:
+                    cherrypy.log('Expired or malicious session %r; '
+                                 'making a new one' % id, 'TOOLS.SESSIONS')
                 # Expired or malicious session. Make a new one.
                 # See http://www.cherrypy.org/ticket/709.
                 self.id = None
             if self.loaded:
                 t = datetime.timedelta(seconds = self.timeout * 60)
                 expiration_time = datetime.datetime.now() + t
+                if self.debug:
+                    cherrypy.log('Saving with expiry %s' % expiration_time,
+                                 'TOOLS.SESSIONS')
                 self._save(expiration_time)
             
         finally:
         data = self._load()
         # data is either None or a tuple (session_data, expiration_time)
         if data is None or data[1] < datetime.datetime.now():
-            # Expired session: flush session data
+            if self.debug:
+                cherrypy.log('Expired session, flushing data', 'TOOLS.SESSIONS')
             self._data = {}
         else:
             self._data = data[0]
 
 def init(storage_type='ram', path=None, path_header=None, name='session_id',
          timeout=60, domain=None, secure=False, clean_freq=5,
-         persistent=True, **kwargs):
+         persistent=True, debug=False, **kwargs):
     """Initialize session object (using cookies).
     
     storage_type: one of 'ram', 'file', 'postgresql'. This will be used
     id = None
     if name in request.cookie:
         id = request.cookie[name].value
+        if debug:
+            cherrypy.log('ID obtained from request.cookie: %r' % id,
+                         'TOOLS.SESSIONS')
     
     # Find the storage class and call setup (first time only).
     storage_class = storage_type.title() + 'Session'
     kwargs['timeout'] = timeout
     kwargs['clean_freq'] = clean_freq
     cherrypy.serving.session = sess = storage_class(id, **kwargs)
+    sess.debug = debug
     def update_cookie(id):
         """Update the cookie every time the session id changes."""
         cherrypy.serving.response.cookie[name] = id

cherrypy/lib/static.py

 from cherrypy.lib import cptools, httputil, file_generator_limited
 
 
-def serve_file(path, content_type=None, disposition=None, name=None):
+def serve_file(path, content_type=None, disposition=None, name=None, debug=False):
     """Set status, headers, and body in order to serve the given path.
     
     The Content-Type header will be set to the content_type arg, if provided.
     # variety of paths). If using tools.staticdir, you can make your relative
     # paths become absolute by supplying a value for "tools.staticdir.root".
     if not os.path.isabs(path):
-        raise ValueError("'%s' is not an absolute path." % path)
+        msg = "'%s' is not an absolute path." % path
+        if debug:
+            cherrypy.log(msg, 'TOOLS.STATICFILE')
+        raise ValueError(msg)
     
     try:
         st = os.stat(path)
     except OSError:
+        if debug:
+            cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC')
         raise cherrypy.NotFound()
     
     # Check if path is a directory.
     if stat.S_ISDIR(st.st_mode):
         # Let the caller deal with it as they like.
+        if debug:
+            cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC')
         raise cherrypy.NotFound()
     
     # Set the Last-Modified response header, so that
         content_type = mimetypes.types_map.get(ext, None)
     if content_type is not None:
         response.headers['Content-Type'] = content_type
+    if debug:
+        cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC')
     
+    cd = None
     if disposition is not None:
         if name is None:
             name = os.path.basename(path)
         cd = '%s; filename="%s"' % (disposition, name)
         response.headers["Content-Disposition"] = cd
+    if debug:
+        cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
     
     # Set Content-Length and use an iterable (file object)
     #   this way CP won't load the whole file in memory
     content_length = st.st_size
     fileobj = open(path, 'rb')
-    return _serve_fileobj(fileobj, content_type, content_length)
+    return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
 
-def serve_fileobj(fileobj, content_type=None, disposition=None, name=None):
+def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
+                  debug=False):
     """Set status, headers, and body in order to serve the given file object.
     
     The Content-Type header will be set to the content_type arg, if provided.
     try:
         st = os.fstat(fileobj.fileno())
     except AttributeError:
+        if debug:
+            cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC')
         content_length = None
     else:
         # Set the Last-Modified response header, so that
     
     if content_type is not None:
         response.headers['Content-Type'] = content_type
+    if debug:
+        cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC')
     
+    cd = None
     if disposition is not None:
         if name is None:
             cd = disposition
         else:
             cd = '%s; filename="%s"' % (disposition, name)
         response.headers["Content-Disposition"] = cd
+    if debug:
+        cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
     
-    return _serve_fileobj(fileobj, content_type, content_length)
+    return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
 
-def _serve_fileobj(fileobj, content_type, content_length):
+def _serve_fileobj(fileobj, content_type, content_length, debug=False):
     """Internal. Set response.body to the given file object, perhaps ranged."""
     response = cherrypy.serving.response
     
         if r == []:
             response.headers['Content-Range'] = "bytes */%s" % content_length
             message = "Invalid Range (first-byte-pos greater than Content-Length)"
+            if debug:
+                cherrypy.log(message, 'TOOLS.STATIC')
             raise cherrypy.HTTPError(416, message)
         
         if r:
                 if stop > content_length:
                     stop = content_length
                 r_len = stop - start
+                if debug:
+                    cherrypy.log('Single part; start: %r, stop: %r' % (start, stop),
+                                 'TOOLS.STATIC')
                 response.status = "206 Partial Content"
                 response.headers['Content-Range'] = (
                     "bytes %s-%s/%s" % (start, stop - 1, content_length))
                     yield "\r\n"
                     
                     for start, stop in r:
+                        if debug:
+                            cherrypy.log('Multipart; start: %r, stop: %r' % (start, stop),
+                                         'TOOLS.STATIC')
                         yield "--" + boundary
                         yield "\r\nContent-type: %s" % content_type
                         yield ("\r\nContent-range: bytes %s-%s/%s\r\n\r\n"
                     yield "\r\n"
                 response.body = file_ranges()
             return response.body
+        else:
+            if debug:
+                cherrypy.log('No byteranges requested', 'TOOLS.STATIC')
     
     # Set Content-Length and use an iterable (file object)
     #   this way CP won't load the whole file in memory
     return serve_file(path, "application/x-download", "attachment", name)
 
 
-def _attempt(filename, content_types):
+def _attempt(filename, content_types, debug=False):
+    if debug:
+        cherrypy.log('Attempting %r (content_types %r)' %
+                     (filename, content_types), 'TOOLS.STATICDIR')
     try:
         # you can set the content types for a
         # complete directory per extension
         if content_types:
             r, ext = os.path.splitext(filename)
             content_type = content_types.get(ext[1:], None)
-        serve_file(filename, content_type=content_type)
+        serve_file(filename, content_type=content_type, debug=debug)
         return True
     except cherrypy.NotFound:
         # If we didn't find the static file, continue handling the
         # request. We might find a dynamic handler instead.
+        if debug:
+            cherrypy.log('NotFound', 'TOOLS.STATICFILE')
         return False
 
-def staticdir(section, dir, root="", match="", content_types=None, index=""):
+def staticdir(section, dir, root="", match="", content_types=None, index="",
+              debug=False):
     """Serve a static resource from the given (root +) dir.
     
     If 'match' is given, request.path_info will be searched for the given
     """
     request = cherrypy.serving.request
     if request.method not in ('GET', 'HEAD'):
+        if debug:
+            cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICDIR')
         return False
     
     if match and not re.search(match, request.path_info):
+        if debug:
+            cherrypy.log('request.path_info %r does not match pattern %r' %
+                         (request.path_info, match), 'TOOLS.STATICDIR')
         return False
     
     # Allow the use of '~' to refer to a user's home directory.
     if not os.path.isabs(dir):
         if not root:
             msg = "Static dir requires an absolute dir (or root)."
+            if debug:
+                cherrypy.log(msg, 'TOOLS.STATICDIR')
             raise ValueError(msg)
         dir = os.path.join(root, dir)
     
     
     # If branch is "", filename will end in a slash
     filename = os.path.join(dir, branch)
-    cherrypy.log('Checking file %r to fulfill %r' %
-                 (filename, request.path_info),
-                 context='tools.staticdir', severity=logging.DEBUG)
+    if debug:
+        cherrypy.log('Checking file %r to fulfill %r' %
+                     (filename, request.path_info), 'TOOLS.STATICDIR')
     
     # There's a chance that the branch pulled from the URL might
     # have ".." or similar uplevel attacks in it. Check that the final
                 request.is_index = filename[-1] in (r"\/")
     return handled
 
-def staticfile(filename, root=None, match="", content_types=None):
+def staticfile(filename, root=None, match="", content_types=None, debug=False):
     """Serve a static resource from the given (root +) filename.
     
     If 'match' is given, request.path_info will be searched for the given
     """
     request = cherrypy.serving.request
     if request.method not in ('GET', 'HEAD'):
+        if debug:
+            cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICFILE')
         return False
     
     if match and not re.search(match, request.path_info):
+        if debug:
+            cherrypy.log('request.path_info %r does not match pattern %r' %
+                         (request.path_info, match), 'TOOLS.STATICFILE')
         return False
     
     # If filename is relative, make absolute using "root".
     if not os.path.isabs(filename):
         if not root:
             msg = "Static tool requires an absolute filename (got '%s')." % filename
+            if debug:
+                cherrypy.log(msg, 'TOOLS.STATICFILE')
             raise ValueError(msg)
         filename = os.path.join(root, filename)
     
-    return _attempt(filename, content_types)
+    return _attempt(filename, content_types, debug=debug)