Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Cherrypy update

  • Loading branch information...
commit f20a6f69cde9a7349fd586b84269614cc250a6c3 1 parent af7eac8
@RuudBurger authored
Showing with 5,398 additions and 2,440 deletions.
  1. +25 −0 cherrypy/LICENSE.txt
  2. +10 −6 cherrypy/__init__.py
  3. 0  cherrypy/_cpchecker.py
  4. +54 −19 cherrypy/_cpcompat.py
  5. 0  cherrypy/_cpconfig.py
  6. +21 −7 cherrypy/_cpdispatch.py
  7. +28 −25 cherrypy/_cperror.py
  8. +66 −19 cherrypy/_cplogging.py
  9. +13 −2 cherrypy/_cpmodpy.py
  10. 0  cherrypy/_cpnative_server.py
  11. +37 −13 cherrypy/_cpreqbody.py
  12. +39 −9 cherrypy/_cprequest.py
  13. +17 −7 cherrypy/_cpserver.py
  14. 0  cherrypy/_cpthreadinglocal.py
  15. +2 −2 cherrypy/_cptools.py
  16. +23 −12 cherrypy/_cptree.py
  17. +78 −17 cherrypy/_cpwsgi.py
  18. +11 −2 cherrypy/_cpwsgi_server.py
  19. BIN  cherrypy/favicon.ico
  20. +1 −1  cherrypy/lib/__init__.py
  21. 0  cherrypy/lib/auth.py
  22. 0  cherrypy/lib/auth_basic.py
  23. 0  cherrypy/lib/auth_digest.py
  24. 0  cherrypy/lib/caching.py
  25. 0  cherrypy/lib/covercp.py
  26. +4 −3 cherrypy/lib/cpstats.py
  27. +10 −4 cherrypy/lib/cptools.py
  28. 0  cherrypy/lib/encoding.py
  29. +214 −0 cherrypy/lib/gctools.py
  30. 0  cherrypy/lib/http.py
  31. 0  cherrypy/lib/httpauth.py
  32. +44 −7 cherrypy/lib/httputil.py
  33. +1 −1  cherrypy/lib/jsontools.py
  34. 0  cherrypy/lib/profiler.py
  35. +151 −17 cherrypy/lib/reprconf.py
  36. +56 −17 cherrypy/lib/sessions.py
  37. +12 −1 cherrypy/lib/static.py
  38. +16 −10 cherrypy/lib/{xmlrpc.py → xmlrpcutil.py}
  39. 0  cherrypy/process/__init__.py
  40. +7 −5 cherrypy/process/plugins.py
  41. +15 −6 cherrypy/process/servers.py
  42. 0  cherrypy/process/win32.py
  43. +48 −9 cherrypy/process/wspbus.py
  44. +11 −2,216 cherrypy/wsgiserver/__init__.py
  45. +22 −3 cherrypy/wsgiserver/ssl_builtin.py
  46. 0  cherrypy/wsgiserver/ssl_pyopenssl.py
  47. +2,322 −0 cherrypy/wsgiserver/wsgiserver2.py
  48. +2,040 −0 cherrypy/wsgiserver/wsgiserver3.py
View
25 cherrypy/LICENSE.txt
@@ -0,0 +1,25 @@
+Copyright (c) 2004-2011, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the CherryPy Team nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
16 cherrypy/__init__.py 100644 → 100755
@@ -57,10 +57,10 @@
http://www.cherrypy.org/wiki/CherryPySpec
"""
-__version__ = "3.2.0"
+__version__ = "3.2.2"
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
-from cherrypy._cpcompat import basestring, unicodestr
+from cherrypy._cpcompat import basestring, unicodestr, set
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError
@@ -89,17 +89,21 @@
engine = process.bus
-# Timeout monitor
+# Timeout monitor. We add two channels to the engine
+# to which cherrypy.Application will publish.
+engine.listeners['before_request'] = set()
+engine.listeners['after_request'] = set()
+
class _TimeoutMonitor(process.plugins.Monitor):
def __init__(self, bus):
self.servings = []
process.plugins.Monitor.__init__(self, bus, self.run)
- def acquire(self):
+ def before_request(self):
self.servings.append((serving.request, serving.response))
- def release(self):
+ def after_request(self):
try:
self.servings.remove((serving.request, serving.response))
except ValueError:
@@ -585,7 +589,7 @@ def url(path="", qs="", script_name=None, base=None, relative=None):
elif relative:
# "A relative reference that does not begin with a scheme name
# or a slash character is termed a relative-path reference."
- old = url().split('/')[:-1]
+ old = url(relative=False).split('/')[:-1]
new = newurl.split('/')
while old and new:
a, b = old[0], new[0]
View
0  cherrypy/_cpchecker.py 100644 → 100755
File mode changed
View
73 cherrypy/_cpcompat.py 100644 → 100755
@@ -16,43 +16,67 @@
output.
"""
import os
+import re
import sys
if sys.version_info >= (3, 0):
+ py3k = True
bytestr = bytes
unicodestr = str
nativestr = unicodestr
basestring = (bytes, str)
- def ntob(n, encoding = 'ISO-8859-1'):
+ def ntob(n, encoding='ISO-8859-1'):
"""Return the given native string as a byte string in the given encoding."""
# In Python 3, the native string type is unicode
return n.encode(encoding)
- def ntou(n, encoding = 'ISO-8859-1'):
+ def ntou(n, encoding='ISO-8859-1'):
"""Return the given native string as a unicode string with the given encoding."""
# In Python 3, the native string type is unicode
return n
+ def tonative(n, encoding='ISO-8859-1'):
+ """Return the given string as a native string in the given encoding."""
+ # In Python 3, the native string type is unicode
+ if isinstance(n, bytes):
+ return n.decode(encoding)
+ return n
# type("")
from io import StringIO
# bytes:
from io import BytesIO as BytesIO
else:
# Python 2
+ py3k = False
bytestr = str
unicodestr = unicode
nativestr = bytestr
basestring = basestring
- def ntob(n, encoding = 'ISO-8859-1'):
+ def ntob(n, encoding='ISO-8859-1'):
"""Return the given native string as a byte string in the given encoding."""
# In Python 2, the native string type is bytes. Assume it's already
# in the given encoding, which for ISO-8859-1 is almost always what
# was intended.
return n
- def ntou(n, encoding = 'ISO-8859-1'):
+ def ntou(n, encoding='ISO-8859-1'):
"""Return the given native string as a unicode string with the given encoding."""
- # In Python 2, the native string type is bytes. Assume it's already
- # in the given encoding, which for ISO-8859-1 is almost always what
- # was intended.
+ # In Python 2, the native string type is bytes.
+ # First, check for the special encoding 'escape'. The test suite uses this
+ # to signal that it wants to pass a string with embedded \uXXXX escapes,
+ # but without having to prefix it with u'' for Python 2, but no prefix
+ # for Python 3.
+ if encoding == 'escape':
+ return unicode(
+ re.sub(r'\\u([0-9a-zA-Z]{4})',
+ lambda m: unichr(int(m.group(1), 16)),
+ n.decode('ISO-8859-1')))
+ # Assume it's already in the given encoding, which for ISO-8859-1 is almost
+ # always what was intended.
return n.decode(encoding)
+ def tonative(n, encoding='ISO-8859-1'):
+ """Return the given string as a native string in the given encoding."""
+ # In Python 2, the native string type is bytes.
+ if isinstance(n, unicode):
+ return n.encode(encoding)
+ return n
try:
# type("")
from cStringIO import StringIO
@@ -76,7 +100,7 @@ def ntou(n, encoding = 'ISO-8859-1'):
# the legacy API of base64
from base64 import decodestring as _base64_decodebytes
-def base64_decode(n, encoding = 'ISO-8859-1'):
+def base64_decode(n, encoding='ISO-8859-1'):
"""Return the native string base64-decoded (as a native string)."""
if isinstance(n, unicodestr):
b = n.encode(encoding)
@@ -117,13 +141,13 @@ def reversed(x):
i -= 1
yield x[i]
-if sys.version_info >= (3, 0):
+try:
# Python 3
from urllib.parse import urljoin, urlencode
from urllib.parse import quote, quote_plus
from urllib.request import unquote, urlopen
from urllib.request import parse_http_list, parse_keqv_list
-else:
+except ImportError:
# Python 2
from urlparse import urljoin
from urllib import urlencode, urlopen
@@ -186,6 +210,18 @@ def reversed(x):
from http.server import BaseHTTPRequestHandler
try:
+ # Python 2. We have to do it in this order so Python 2 builds
+ # don't try to import the 'http' module from cherrypy.lib
+ from httplib import HTTPSConnection
+except ImportError:
+ try:
+ # Python 3
+ from http.client import HTTPSConnection
+ except ImportError:
+ # Some platforms which don't have SSL don't expose HTTPSConnection
+ HTTPSConnection = None
+
+try:
# Python 2
xrange = xrange
except NameError:
@@ -207,20 +243,20 @@ def set_daemon(t, val):
try:
from email.utils import formatdate
- def HTTPDate(timeval = None):
- return formatdate(timeval, usegmt = True)
+ def HTTPDate(timeval=None):
+ return formatdate(timeval, usegmt=True)
except ImportError:
from rfc822 import formatdate as HTTPDate
-if sys.version_info >= (3, 0):
+try:
# Python 3
from urllib.parse import unquote as parse_unquote
- def unquote_qs(atom, encoding, errors = 'strict'):
- return parse_unquote(atom.replace('+', ' '), encoding = encoding, errors = errors)
-else:
+ def unquote_qs(atom, encoding, errors='strict'):
+ return parse_unquote(atom.replace('+', ' '), encoding=encoding, errors=errors)
+except ImportError:
# Python 2
from urllib import unquote as parse_unquote
- def unquote_qs(atom, encoding, errors = 'strict'):
+ def unquote_qs(atom, encoding, errors='strict'):
return parse_unquote(atom.replace('+', ' ')).decode(encoding, errors)
try:
@@ -229,7 +265,7 @@ def unquote_qs(atom, encoding, errors = 'strict'):
json_decode = json.JSONDecoder().decode
json_encode = json.JSONEncoder().iterencode
except ImportError:
- if sys.version_info >= (3, 0):
+ if py3k:
# Python 3.0: json is part of the standard library,
# but outputs unicode. We need bytes.
import json
@@ -280,4 +316,3 @@ def random20():
# Python 2
def next(i):
return i.next()
-
View
0  cherrypy/_cpconfig.py 100644 → 100755
File mode changed
View
28 cherrypy/_cpdispatch.py 100644 → 100755
@@ -12,8 +12,13 @@
import string
import sys
import types
+try:
+ classtype = (type, types.ClassType)
+except AttributeError:
+ classtype = type
import cherrypy
+from cherrypy._cpcompat import set
class PageHandler(object):
@@ -197,8 +202,18 @@ def _set_kwargs(self, kwargs):
'cherrypy.request.params copied in)')
-punctuation_to_underscores = string.maketrans(
- string.punctuation, '_' * len(string.punctuation))
+if sys.version_info < (3, 0):
+ punctuation_to_underscores = string.maketrans(
+ string.punctuation, '_' * len(string.punctuation))
+ def validate_translator(t):
+ if not isinstance(t, str) or len(t) != 256:
+ raise ValueError("The translate argument must be a str of len 256.")
+else:
+ punctuation_to_underscores = str.maketrans(
+ string.punctuation, '_' * len(string.punctuation))
+ def validate_translator(t):
+ if not isinstance(t, dict):
+ raise ValueError("The translate argument must be a dict.")
class Dispatcher(object):
"""CherryPy Dispatcher which walks a tree of objects to find a handler.
@@ -222,8 +237,7 @@ class Dispatcher(object):
def __init__(self, dispatch_method_name=None,
translate=punctuation_to_underscores):
- if not isinstance(translate, str) or len(translate) != 256:
- raise ValueError("The translate argument must be a str of len 256.")
+ validate_translator(translate)
self.translate = translate
if dispatch_method_name:
self.dispatch_method_name = dispatch_method_name
@@ -524,7 +538,7 @@ def merge(nodeconf):
controller = result.get('controller')
controller = self.controllers.get(controller, controller)
if controller:
- if isinstance(controller, (type, types.ClassType)):
+ if isinstance(controller, classtype):
controller = controller()
# Get config from the controller.
if hasattr(controller, "_cp_config"):
@@ -550,9 +564,9 @@ def merge(nodeconf):
def XMLRPCDispatcher(next_dispatcher=Dispatcher()):
- from cherrypy.lib import xmlrpc
+ from cherrypy.lib import xmlrpcutil
def xmlrpc_dispatch(path_info):
- path_info = xmlrpc.patched_path(path_info)
+ path_info = xmlrpcutil.patched_path(path_info)
return next_dispatcher(path_info)
return xmlrpc_dispatch
View
53 cherrypy/_cperror.py 100644 → 100755
@@ -107,7 +107,7 @@ class Root:
from cgi import escape as _escape
from sys import exc_info as _exc_info
from traceback import format_exception as _format_exception
-from cherrypy._cpcompat import basestring, iteritems, urljoin as _urljoin
+from cherrypy._cpcompat import basestring, bytestr, iteritems, ntob, tonative, urljoin as _urljoin
from cherrypy.lib import httputil as _httputil
@@ -183,7 +183,7 @@ class HTTPRedirect(CherryPyException):
"""The list of URL's to emit."""
encoding = 'utf-8'
- """The encoding when passed urls are unicode objects"""
+ """The encoding when passed urls are not native strings"""
def __init__(self, urls, status=None, encoding=None):
import cherrypy
@@ -194,8 +194,7 @@ def __init__(self, urls, status=None, encoding=None):
abs_urls = []
for url in urls:
- if isinstance(url, unicode):
- url = url.encode(encoding or self.encoding)
+ url = tonative(url, encoding or self.encoding)
# Note that urljoin will "do the right thing" whether url is:
# 1. a complete URL with host (e.g. "http://www.example.com/test")
@@ -248,7 +247,7 @@ def set_response(self):
307: "This resource has moved temporarily to <a href='%s'>%s</a>.",
}[status]
msgs = [msg % (u, u) for u in self.urls]
- response.body = "<br />\n".join(msgs)
+ response.body = ntob("<br />\n".join(msgs), 'utf-8')
# Previous code may have set C-L, so we have to reset it
# (allow finalize to set it).
response.headers.pop('Content-Length', None)
@@ -341,8 +340,8 @@ def __init__(self, status=500, message=None):
self.status = status
try:
self.code, self.reason, defaultmsg = _httputil.valid_status(status)
- except ValueError, x:
- raise self.__class__(500, x.args[0])
+ except ValueError:
+ raise self.__class__(500, _exc_info()[1].args[0])
if self.code < 400 or self.code > 599:
raise ValueError("status must be between 400 and 599.")
@@ -373,8 +372,8 @@ def set_response(self):
response.headers['Content-Type'] = "text/html;charset=utf-8"
response.headers.pop('Content-Length', None)
- content = self.get_error_page(self.status, traceback=tb,
- message=self._message)
+ content = ntob(self.get_error_page(self.status, traceback=tb,
+ message=self._message), 'utf-8')
response.body = content
_be_ie_unfriendly(self.code)
@@ -442,8 +441,8 @@ def get_error_page(status, **kwargs):
try:
code, reason, message = _httputil.valid_status(status)
- except ValueError, x:
- raise cherrypy.HTTPError(500, x.args[0])
+ except ValueError:
+ raise cherrypy.HTTPError(500, _exc_info()[1].args[0])
# We can't use setdefault here, because some
# callers send None for kwarg values.
@@ -470,7 +469,8 @@ def get_error_page(status, **kwargs):
if hasattr(error_page, '__call__'):
return error_page(**kwargs)
else:
- return open(error_page, 'rb').read() % kwargs
+ data = open(error_page, 'rb').read()
+ return tonative(data) % kwargs
except:
e = _format_exception(*_exc_info())[-1]
m = kwargs['message']
@@ -508,19 +508,22 @@ def _be_ie_unfriendly(status):
if l and l < s:
# IN ADDITION: the response must be written to IE
# in one chunk or it will still get replaced! Bah.
- content = content + (" " * (s - l))
+ content = content + (ntob(" ") * (s - l))
response.body = content
response.headers['Content-Length'] = str(len(content))
def format_exc(exc=None):
"""Return exc (or sys.exc_info if None), formatted."""
- if exc is None:
- exc = _exc_info()
- if exc == (None, None, None):
- return ""
- import traceback
- return "".join(traceback.format_exception(*exc))
+ try:
+ if exc is None:
+ exc = _exc_info()
+ if exc == (None, None, None):
+ return ""
+ import traceback
+ return "".join(traceback.format_exception(*exc))
+ finally:
+ del exc
def bare_error(extrabody=None):
"""Produce status, headers, body for a critical error.
@@ -539,15 +542,15 @@ def bare_error(extrabody=None):
# it cannot be allowed to fail. Therefore, don't add to it!
# In particular, don't call any other CP functions.
- body = "Unrecoverable error in the server."
+ body = ntob("Unrecoverable error in the server.")
if extrabody is not None:
- if not isinstance(extrabody, str):
+ if not isinstance(extrabody, bytestr):
extrabody = extrabody.encode('utf-8')
- body += "\n" + extrabody
+ body += ntob("\n") + extrabody
- return ("500 Internal Server Error",
- [('Content-Type', 'text/plain'),
- ('Content-Length', str(len(body)))],
+ return (ntob("500 Internal Server Error"),
+ [(ntob('Content-Type'), ntob('text/plain')),
+ (ntob('Content-Length'), ntob(str(len(body)),'ISO-8859-1'))],
[body])
View
85 cherrypy/_cplogging.py 100644 → 100755
@@ -109,6 +109,20 @@
import cherrypy
from cherrypy import _cperror
+from cherrypy._cpcompat import ntob, py3k
+
+
+class NullHandler(logging.Handler):
+ """A no-op logging handler to silence the logging.lastResort handler."""
+
+ def handle(self, record):
+ pass
+
+ def emit(self, record):
+ pass
+
+ def createLock(self):
+ self.lock = None
class LogManager(object):
@@ -127,8 +141,12 @@ class LogManager(object):
access_log = None
"""The actual :class:`logging.Logger` instance for access messages."""
- access_log_format = \
- '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
+ if py3k:
+ access_log_format = \
+ '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
+ else:
+ access_log_format = \
+ '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
logger_root = None
"""The "top-level" logger name.
@@ -152,8 +170,13 @@ def __init__(self, appid=None, logger_root="cherrypy"):
self.access_log = logging.getLogger("%s.access.%s" % (logger_root, appid))
self.error_log.setLevel(logging.INFO)
self.access_log.setLevel(logging.INFO)
+
+ # Silence the no-handlers "warning" (stderr write!) in stdlib logging
+ self.error_log.addHandler(NullHandler())
+ self.access_log.addHandler(NullHandler())
+
cherrypy.engine.subscribe('graceful', self.reopen_files)
-
+
def reopen_files(self):
"""Close and reopen all file handlers."""
for log in (self.error_log, self.access_log):
@@ -206,7 +229,9 @@ def access(self):
if response.output_status is None:
status = "-"
else:
- status = response.output_status.split(" ", 1)[0]
+ status = response.output_status.split(ntob(" "), 1)[0]
+ if py3k:
+ status = status.decode('ISO-8859-1')
atoms = {'h': remote.name or remote.ip,
'l': '-',
@@ -218,21 +243,43 @@ def access(self):
'f': dict.get(inheaders, 'Referer', ''),
'a': dict.get(inheaders, 'User-Agent', ''),
}
- for k, v in atoms.items():
- if isinstance(v, unicode):
- v = v.encode('utf8')
- elif not isinstance(v, str):
- v = str(v)
- # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
- # and backslash for us. All we have to do is strip the quotes.
- v = repr(v)[1:-1]
- # Escape double-quote.
- atoms[k] = v.replace('"', '\\"')
-
- try:
- self.access_log.log(logging.INFO, self.access_log_format % atoms)
- except:
- self(traceback=True)
+ if py3k:
+ for k, v in atoms.items():
+ if not isinstance(v, str):
+ v = str(v)
+ v = v.replace('"', '\\"').encode('utf8')
+ # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
+ # and backslash for us. All we have to do is strip the quotes.
+ v = repr(v)[2:-1]
+
+ # in python 3.0 the repr of bytes (as returned by encode)
+ # uses double \'s. But then the logger escapes them yet, again
+ # resulting in quadruple slashes. Remove the extra one here.
+ v = v.replace('\\\\', '\\')
+
+ # Escape double-quote.
+ atoms[k] = v
+
+ try:
+ self.access_log.log(logging.INFO, self.access_log_format.format(**atoms))
+ except:
+ self(traceback=True)
+ else:
+ for k, v in atoms.items():
+ if isinstance(v, unicode):
+ v = v.encode('utf8')
+ elif not isinstance(v, str):
+ v = str(v)
+ # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc
+ # and backslash for us. All we have to do is strip the quotes.
+ v = repr(v)[1:-1]
+ # Escape double-quote.
+ atoms[k] = v.replace('"', '\\"')
+
+ try:
+ self.access_log.log(logging.INFO, self.access_log_format % atoms)
+ except:
+ self(traceback=True)
def time(self):
"""Return now() in Apache Common Log Format (no timezone)."""
View
15 cherrypy/_cpmodpy.py 100644 → 100755
@@ -224,7 +224,7 @@ def handler(req):
qs = ir.query_string
rfile = BytesIO()
- send_response(req, response.status, response.header_list,
+ send_response(req, response.output_status, response.header_list,
response.body, response.stream)
finally:
app.release_serving()
@@ -266,11 +266,22 @@ def send_response(req, status, headers, body, stream=False):
import os
import re
+try:
+ import subprocess
+ def popen(fullcmd):
+ p = subprocess.Popen(fullcmd, shell=True,
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+ close_fds=True)
+ return p.stdout
+except ImportError:
+ def popen(fullcmd):
+ pipein, pipeout = os.popen4(fullcmd)
+ return pipeout
def read_process(cmd, args=""):
fullcmd = "%s %s" % (cmd, args)
- pipein, pipeout = os.popen4(fullcmd)
+ pipeout = popen(fullcmd)
try:
firstline = pipeout.readline()
if (re.search(ntob("(not recognized|No such file|not found)"), firstline,
View
0  cherrypy/_cpnative_server.py 100644 → 100755
File mode changed
View
50 cherrypy/_cpreqbody.py 100644 → 100755
@@ -101,10 +101,28 @@ def json_processor(entity):
Note that you can only replace the ``processors`` dict wholesale this way, not update the existing one.
"""
+try:
+ from io import DEFAULT_BUFFER_SIZE
+except ImportError:
+ DEFAULT_BUFFER_SIZE = 8192
import re
import sys
import tempfile
-from urllib import unquote_plus
+try:
+ from urllib import unquote_plus
+except ImportError:
+ def unquote_plus(bs):
+ """Bytes version of urllib.parse.unquote_plus."""
+ bs = bs.replace(ntob('+'), ntob(' '))
+ atoms = bs.split(ntob('%'))
+ for i in range(1, len(atoms)):
+ item = atoms[i]
+ try:
+ pct = int(item[:2], 16)
+ atoms[i] = bytes([pct]) + item[2:]
+ except ValueError:
+ pass
+ return ntob('').join(atoms)
import cherrypy
from cherrypy._cpcompat import basestring, ntob, ntou
@@ -399,7 +417,6 @@ def __init__(self, fp, headers, params=None, parts=None):
# Copy the class 'attempt_charsets', prepending any Content-Type charset
dec = self.content_type.params.get("charset", None)
if dec:
- #dec = dec.decode('ISO-8859-1')
self.attempt_charsets = [dec] + [c for c in self.attempt_charsets
if c != dec]
else:
@@ -446,11 +463,14 @@ def readlines(self, sizehint=None):
def __iter__(self):
return self
- def next(self):
+ def __next__(self):
line = self.readline()
if not line:
raise StopIteration
return line
+
+ def next(self):
+ return self.__next__()
def read_into_file(self, fp_out=None):
"""Read the request body into fp_out (or make_file() if None). Return fp_out."""
@@ -671,13 +691,16 @@ def read_into_file(self, fp_out=None):
Entity.part_class = Part
-
-class Infinity(object):
- def __cmp__(self, other):
- return 1
- def __sub__(self, other):
- return self
-inf = Infinity()
+try:
+ inf = float('inf')
+except ValueError:
+ # Python 2.4 and lower
+ class Infinity(object):
+ def __cmp__(self, other):
+ return 1
+ def __sub__(self, other):
+ return self
+ inf = Infinity()
comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding',
@@ -689,7 +712,7 @@ def __sub__(self, other):
class SizedReader:
- def __init__(self, fp, length, maxbytes, bufsize=8192, has_trailers=False):
+ def __init__(self, fp, length, maxbytes, bufsize=DEFAULT_BUFFER_SIZE, has_trailers=False):
# Wrap our fp in a buffer so peek() works
self.fp = fp
self.length = length
@@ -930,8 +953,9 @@ def process(self):
request_params = self.request_params
for key, value in self.params.items():
# Python 2 only: keyword arguments must be byte strings (type 'str').
- if isinstance(key, unicode):
- key = key.encode('ISO-8859-1')
+ if sys.version_info < (3, 0):
+ if isinstance(key, unicode):
+ key = key.encode('ISO-8859-1')
if key in request_params:
if not isinstance(request_params[key], list):
View
48 cherrypy/_cprequest.py 100644 → 100755
@@ -6,7 +6,7 @@
import cherrypy
from cherrypy._cpcompat import basestring, copykeys, ntob, unicodestr
-from cherrypy._cpcompat import SimpleCookie, CookieError
+from cherrypy._cpcompat import SimpleCookie, CookieError, py3k
from cherrypy import _cpreqbody, _cpconfig
from cherrypy._cperror import format_exc, bare_error
from cherrypy.lib import httputil, file_generator
@@ -49,7 +49,12 @@ def __init__(self, callback, failsafe=None, priority=None, **kwargs):
self.kwargs = kwargs
+ def __lt__(self, other):
+ # Python 3
+ return self.priority < other.priority
+
def __cmp__(self, other):
+ # Python 2
return cmp(self.priority, other.priority)
def __call__(self):
@@ -104,7 +109,7 @@ def run(self, point):
exc = sys.exc_info()[1]
cherrypy.log(traceback=True, severity=40)
if exc:
- raise
+ raise exc
def __copy__(self):
newmap = self.__class__()
@@ -488,14 +493,20 @@ def close(self):
self.stage = 'close'
def run(self, method, path, query_string, req_protocol, headers, rfile):
- """Process the Request. (Core)
+ r"""Process the Request. (Core)
method, path, query_string, and req_protocol should be pulled directly
from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
path
This should be %XX-unquoted, but query_string should not be.
- They both MUST be byte strings, not unicode strings.
+
+ When using Python 2, they both MUST be byte strings,
+ not unicode strings.
+
+ When using Python 3, they both MUST be unicode strings,
+ not byte strings, and preferably not bytes \x00-\xFF
+ disguised as unicode.
headers
A list of (name, value) tuples.
@@ -676,10 +687,11 @@ def process_query_string(self):
self.query_string_encoding)
# Python 2 only: keyword arguments must be byte strings (type 'str').
- for key, value in p.items():
- if isinstance(key, unicode):
- del p[key]
- p[key.encode(self.query_string_encoding)] = value
+ if not py3k:
+ for key, value in p.items():
+ if isinstance(key, unicode):
+ del p[key]
+ p[key.encode(self.query_string_encoding)] = value
self.params.update(p)
def process_headers(self):
@@ -770,6 +782,10 @@ def _get_body_params(self):
class ResponseBody(object):
"""The body of the HTTP response (the response entity)."""
+ if py3k:
+ unicode_err = ("Page handlers MUST return bytes. Use tools.encode "
+ "if you wish to return unicode.")
+
def __get__(self, obj, objclass=None):
if obj is None:
# When calling on the class instead of an instance...
@@ -779,6 +795,9 @@ def __get__(self, obj, objclass=None):
def __set__(self, obj, value):
# Convert the given value to an iterable object.
+ if py3k and isinstance(value, str):
+ raise ValueError(self.unicode_err)
+
if isinstance(value, basestring):
# strings get wrapped in a list because iterating over a single
# item list is much faster than iterating over every character
@@ -788,6 +807,11 @@ def __set__(self, obj, value):
else:
# [''] doesn't evaluate to False, so replace it with [].
value = []
+ elif py3k and isinstance(value, list):
+ # every item in a list must be bytes...
+ for i, item in enumerate(value):
+ if isinstance(item, str):
+ raise ValueError(self.unicode_err)
# Don't use isinstance here; io.IOBase which has an ABC takes
# 1000 times as long as, say, isinstance(value, str)
elif hasattr(value, 'read'):
@@ -862,7 +886,12 @@ def collapse_body(self):
if isinstance(self.body, basestring):
return self.body
- newbody = ''.join([chunk for chunk in self.body])
+ newbody = []
+ for chunk in self.body:
+ if py3k and not isinstance(chunk, bytes):
+ raise TypeError("Chunk %s is not of type 'bytes'." % repr(chunk))
+ newbody.append(chunk)
+ newbody = ntob('').join(newbody)
self.body = newbody
return newbody
@@ -876,6 +905,7 @@ def finalize(self):
headers = self.headers
+ self.status = "%s %s" % (code, reason)
self.output_status = ntob(str(code), 'ascii') + ntob(" ") + headers.encode(reason)
if self.stream:
View
24 cherrypy/_cpserver.py 100644 → 100755
@@ -4,7 +4,7 @@
import cherrypy
from cherrypy.lib import attributes
-from cherrypy._cpcompat import basestring
+from cherrypy._cpcompat import basestring, py3k
# We import * because we want to export check_port
# et al as attributes of this module.
@@ -98,12 +98,22 @@ def _set_socket_host(self, value):
ssl_private_key = None
"""The filename of the private key to use with SSL."""
- ssl_module = 'pyopenssl'
- """The name of a registered SSL adaptation module to use with the builtin
- WSGI server. Builtin options are 'builtin' (to use the SSL library built
- into recent versions of Python) and 'pyopenssl' (to use the PyOpenSSL
- project, which you must install separately). You may also register your
- own classes in the wsgiserver.ssl_adapters dict."""
+ if py3k:
+ ssl_module = 'builtin'
+ """The name of a registered SSL adaptation module to use with the builtin
+ WSGI server. Builtin options are: 'builtin' (to use the SSL library built
+ into recent versions of Python). You may also register your
+ own classes in the wsgiserver.ssl_adapters dict."""
+ else:
+ ssl_module = 'pyopenssl'
+ """The name of a registered SSL adaptation module to use with the builtin
+ WSGI server. Builtin options are 'builtin' (to use the SSL library built
+ into recent versions of Python) and 'pyopenssl' (to use the PyOpenSSL
+ project, which you must install separately). You may also register your
+ own classes in the wsgiserver.ssl_adapters dict."""
+
+ statistics = False
+ """Turns statistics-gathering on or off for aware HTTP servers."""
nodelay = True
"""If True (the default since 3.1), sets the TCP_NODELAY socket option."""
View
0  cherrypy/_cpthreadinglocal.py 100644 → 100755
File mode changed
View
4 cherrypy/_cptools.py 100644 → 100755
@@ -243,7 +243,7 @@ def _setup(self):
# Builtin tools #
from cherrypy.lib import cptools, encoding, auth, static, jsontools
-from cherrypy.lib import sessions as _sessions, xmlrpc as _xmlrpc
+from cherrypy.lib import sessions as _sessions, xmlrpcutil as _xmlrpc
from cherrypy.lib import caching as _caching
from cherrypy.lib import auth_basic, auth_digest
@@ -367,7 +367,7 @@ def default(self, *vpath, **params):
# http://www.cherrypy.org/ticket/533
# if a method is not found, an xmlrpclib.Fault should be returned
# raising an exception here will do that; see
- # cherrypy.lib.xmlrpc.on_error
+ # cherrypy.lib.xmlrpcutil.on_error
raise Exception('method "%s" is not supported' % attr)
conf = cherrypy.serving.request.toolmaps['tools'].get("xmlrpc", {})
View
35 cherrypy/_cptree.py 100644 → 100755
@@ -1,8 +1,10 @@
"""CherryPy Application and Tree objects."""
import os
+import sys
+
import cherrypy
-from cherrypy._cpcompat import ntou
+from cherrypy._cpcompat import ntou, py3k
from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools
from cherrypy.lib import httputil
@@ -123,8 +125,8 @@ def get_serving(self, local, remote, scheme, sproto):
resp = self.response_class()
cherrypy.serving.load(req, resp)
- cherrypy.engine.timeout_monitor.acquire()
cherrypy.engine.publish('acquire_thread')
+ cherrypy.engine.publish('before_request')
return req, resp
@@ -132,7 +134,7 @@ def release_serving(self):
"""Release the current serving (request and response)."""
req = cherrypy.serving.request
- cherrypy.engine.timeout_monitor.release()
+ cherrypy.engine.publish('after_request')
try:
req.close()
@@ -266,14 +268,23 @@ def __call__(self, environ, start_response):
# Correct the SCRIPT_NAME and PATH_INFO environ entries.
environ = environ.copy()
- if environ.get(u'wsgi.version') == (u'u', 0):
- # Python 2/WSGI u.0: all strings MUST be of type unicode
- enc = environ[u'wsgi.url_encoding']
- environ[u'SCRIPT_NAME'] = sn.decode(enc)
- environ[u'PATH_INFO'] = path[len(sn.rstrip("/")):].decode(enc)
+ if not py3k:
+ if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
+ # Python 2/WSGI u.0: all strings MUST be of type unicode
+ enc = environ[ntou('wsgi.url_encoding')]
+ environ[ntou('SCRIPT_NAME')] = sn.decode(enc)
+ environ[ntou('PATH_INFO')] = path[len(sn.rstrip("/")):].decode(enc)
+ else:
+ # Python 2/WSGI 1.x: all strings MUST be of type str
+ environ['SCRIPT_NAME'] = sn
+ environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
else:
- # Python 2/WSGI 1.x: all strings MUST be of type str
- environ['SCRIPT_NAME'] = sn
- environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
+ if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
+ # Python 3/WSGI u.0: all strings MUST be full unicode
+ environ['SCRIPT_NAME'] = sn
+ environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
+ else:
+ # Python 3/WSGI 1.x: all strings MUST be ISO-8859-1 str
+ environ['SCRIPT_NAME'] = sn.encode('utf-8').decode('ISO-8859-1')
+ environ['PATH_INFO'] = path[len(sn.rstrip("/")):].encode('utf-8').decode('ISO-8859-1')
return app(environ, start_response)
-
View
95 cherrypy/_cpwsgi.py 100644 → 100755
@@ -10,7 +10,7 @@
import sys as _sys
import cherrypy as _cherrypy
-from cherrypy._cpcompat import BytesIO
+from cherrypy._cpcompat import BytesIO, bytestr, ntob, ntou, py3k, unicodestr
from cherrypy import _cperror
from cherrypy.lib import httputil
@@ -19,11 +19,11 @@ def downgrade_wsgi_ux_to_1x(environ):
"""Return a new environ dict for WSGI 1.x from the given WSGI u.x environ."""
env1x = {}
- url_encoding = environ[u'wsgi.url_encoding']
- for k, v in environ.items():
- if k in [u'PATH_INFO', u'SCRIPT_NAME', u'QUERY_STRING']:
+ url_encoding = environ[ntou('wsgi.url_encoding')]
+ for k, v in list(environ.items()):
+ if k in [ntou('PATH_INFO'), ntou('SCRIPT_NAME'), ntou('QUERY_STRING')]:
v = v.encode(url_encoding)
- elif isinstance(v, unicode):
+ elif isinstance(v, unicodestr):
v = v.encode('ISO-8859-1')
env1x[k.encode('ISO-8859-1')] = v
@@ -94,7 +94,8 @@ def __call__(self, environ, start_response):
environ = environ.copy()
try:
return self.nextapp(environ, start_response)
- except _cherrypy.InternalRedirect, ir:
+ except _cherrypy.InternalRedirect:
+ ir = _sys.exc_info()[1]
sn = environ.get('SCRIPT_NAME', '')
path = environ.get('PATH_INFO', '')
qs = environ.get('QUERY_STRING', '')
@@ -152,8 +153,12 @@ def __iter__(self):
self.started_response = True
return self
- def next(self):
- return self.trap(self.iter_response.next)
+ if py3k:
+ def __next__(self):
+ return self.trap(next, self.iter_response)
+ else:
+ def next(self):
+ return self.trap(self.iter_response.next)
def close(self):
if hasattr(self.response, 'close'):
@@ -173,6 +178,11 @@ def trap(self, func, *args, **kwargs):
if not _cherrypy.request.show_tracebacks:
tb = ""
s, h, b = _cperror.bare_error(tb)
+ if py3k:
+ # What fun.
+ s = s.decode('ISO-8859-1')
+ h = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
+ for k, v in h]
if self.started_response:
# Empty our iterable (so future calls raise StopIteration)
self.iter_response = iter([])
@@ -191,7 +201,7 @@ def trap(self, func, *args, **kwargs):
raise
if self.started_response:
- return "".join(b)
+ return ntob("").join(b)
else:
return b
@@ -203,24 +213,52 @@ class AppResponse(object):
"""WSGI response iterable for CherryPy applications."""
def __init__(self, environ, start_response, cpapp):
- if environ.get(u'wsgi.version') == (u'u', 0):
- environ = downgrade_wsgi_ux_to_1x(environ)
- self.environ = environ
self.cpapp = cpapp
try:
+ if not py3k:
+ if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
+ environ = downgrade_wsgi_ux_to_1x(environ)
+ self.environ = environ
self.run()
+
+ r = _cherrypy.serving.response
+
+ outstatus = r.output_status
+ if not isinstance(outstatus, bytestr):
+ raise TypeError("response.output_status is not a byte string.")
+
+ outheaders = []
+ for k, v in r.header_list:
+ if not isinstance(k, bytestr):
+ raise TypeError("response.header_list key %r is not a byte string." % k)
+ if not isinstance(v, bytestr):
+ raise TypeError("response.header_list value %r is not a byte string." % v)
+ outheaders.append((k, v))
+
+ if py3k:
+ # According to PEP 3333, when using Python 3, the response status
+ # and headers must be bytes masquerading as unicode; that is, they
+ # must be of type "str" but are restricted to code points in the
+ # "latin-1" set.
+ outstatus = outstatus.decode('ISO-8859-1')
+ outheaders = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
+ for k, v in outheaders]
+
+ self.iter_response = iter(r.body)
+ self.write = start_response(outstatus, outheaders)
except:
self.close()
raise
- r = _cherrypy.serving.response
- self.iter_response = iter(r.body)
- self.write = start_response(r.output_status, r.header_list)
def __iter__(self):
return self
- def next(self):
- return self.iter_response.next()
+ if py3k:
+ def __next__(self):
+ return next(self.iter_response)
+ else:
+ def next(self):
+ return self.iter_response.next()
def close(self):
"""Close and de-reference the current request and response. (Core)"""
@@ -253,6 +291,29 @@ def run(self):
path = httputil.urljoin(self.environ.get('SCRIPT_NAME', ''),
self.environ.get('PATH_INFO', ''))
qs = self.environ.get('QUERY_STRING', '')
+
+ if py3k:
+ # This isn't perfect; if the given PATH_INFO is in the wrong encoding,
+ # it may fail to match the appropriate config section URI. But meh.
+ old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1')
+ new_enc = self.cpapp.find_config(self.environ.get('PATH_INFO', ''),
+ "request.uri_encoding", 'utf-8')
+ if new_enc.lower() != old_enc.lower():
+ # Even though the path and qs are unicode, the WSGI server is
+ # required by PEP 3333 to coerce them to ISO-8859-1 masquerading
+ # as unicode. So we have to encode back to bytes and then decode
+ # again using the "correct" encoding.
+ try:
+ u_path = path.encode(old_enc).decode(new_enc)
+ u_qs = qs.encode(old_enc).decode(new_enc)
+ except (UnicodeEncodeError, UnicodeDecodeError):
+ # Just pass them through without transcoding and hope.
+ pass
+ else:
+ # Only set transcoded values if they both succeed.
+ path = u_path
+ qs = u_qs
+
rproto = self.environ.get('SERVER_PROTOCOL')
headers = self.translate_headers(self.environ)
rfile = self.environ['wsgi.input']
View
13 cherrypy/_cpwsgi_server.py 100644 → 100755
@@ -37,8 +37,11 @@ def __init__(self, server_adapter=cherrypy.server):
)
self.protocol = self.server_adapter.protocol_version
self.nodelay = self.server_adapter.nodelay
-
- ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
+
+ if sys.version_info >= (3, 0):
+ ssl_module = self.server_adapter.ssl_module or 'builtin'
+ else:
+ ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
if self.server_adapter.ssl_context:
adapter_class = wsgiserver.get_ssl_adapter_class(ssl_module)
self.ssl_adapter = adapter_class(
@@ -52,3 +55,9 @@ def __init__(self, server_adapter=cherrypy.server):
self.server_adapter.ssl_certificate,
self.server_adapter.ssl_private_key,
self.server_adapter.ssl_certificate_chain)
+
+ self.stats['Enabled'] = getattr(self.server_adapter, 'statistics', False)
+
+ def error_log(self, msg="", level=20, traceback=False):
+ cherrypy.engine.log(msg, level, traceback)
+
View
BIN  cherrypy/favicon.ico
Binary file not shown
View
2  cherrypy/lib/__init__.py 100644 → 100755
@@ -1,7 +1,7 @@
"""CherryPy Library"""
# Deprecated in CherryPy 3.2 -- remove in CherryPy 3.3
-from cherrypy.lib.reprconf import _Builder, unrepr, modules, attributes
+from cherrypy.lib.reprconf import unrepr, modules, attributes
class file_generator(object):
"""Yield the given input (a file object) in chunks (default 64k). (Core)"""
View
0  cherrypy/lib/auth.py 100644 → 100755
File mode changed
View
0  cherrypy/lib/auth_basic.py 100644 → 100755
File mode changed
View
0  cherrypy/lib/auth_digest.py 100644 → 100755
File mode changed
View
0  cherrypy/lib/caching.py 100644 → 100755
File mode changed
View
0  cherrypy/lib/covercp.py 100644 → 100755
File mode changed
View
7 cherrypy/lib/cpstats.py 100644 → 100755
@@ -320,20 +320,21 @@ def record_start(self):
def record_stop(self, uriset=None, slow_queries=1.0, slow_queries_count=100,
debug=False, **kwargs):
"""Record the end of a request."""
+ resp = cherrypy.serving.response
w = appstats['Requests'][threading._get_ident()]
r = cherrypy.request.rfile.bytes_read
w['Bytes Read'] = r
appstats['Total Bytes Read'] += r
- if cherrypy.response.stream:
+ if resp.stream:
w['Bytes Written'] = 'chunked'
else:
- cl = int(cherrypy.response.headers.get('Content-Length', 0))
+ cl = int(resp.headers.get('Content-Length', 0))
w['Bytes Written'] = cl
appstats['Total Bytes Written'] += cl
- w['Response Status'] = cherrypy.response.status
+ w['Response Status'] = getattr(resp, 'output_status', None) or resp.status
w['End Time'] = time.time()
p = w['End Time'] - w['Start Time']
View
14 cherrypy/lib/cptools.py 100644 → 100755
@@ -116,7 +116,7 @@ def validate_since():
# Tool code #
def allow(methods=None, debug=False):
- """Raise 405 if request.method not in methods (default GET/HEAD).
+ """Raise 405 if request.method not in methods (default ['GET', 'HEAD']).
The given methods are case-insensitive, and may be in any order.
If only one method is allowed, you may supply a single string;
@@ -151,6 +151,10 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
For running a CP server behind Apache, lighttpd, or other HTTP server.
+ For Apache and lighttpd, you should leave the 'local' argument at the
+ default value of 'X-Forwarded-Host'. For Squid, you probably want to set
+ tools.proxy.local = 'Origin'.
+
If you want the new request.base to include path info (not just the host),
you must explicitly set base to the full base path, and ALSO set 'local'
to '', so that the X-Forwarded-Host request header (which never includes
@@ -581,9 +585,11 @@ def get(self, key, default=None):
self.accessed_headers.add(key)
return _httputil.HeaderMap.get(self, key, default=default)
- def has_key(self, key):
- self.accessed_headers.add(key)
- return _httputil.HeaderMap.has_key(self, key)
+ if hasattr({}, 'has_key'):
+ # Python 2
+ def has_key(self, key):
+ self.accessed_headers.add(key)
+ return _httputil.HeaderMap.has_key(self, key)
def autovary(ignore=None, debug=False):
View
0  cherrypy/lib/encoding.py 100644 → 100755
File mode changed
View
214 cherrypy/lib/gctools.py
@@ -0,0 +1,214 @@
+import gc
+import inspect
+import os
+import sys
+import time
+
+try:
+ import objgraph
+except ImportError:
+ objgraph = None
+
+import cherrypy
+from cherrypy import _cprequest, _cpwsgi
+from cherrypy.process.plugins import SimplePlugin
+
+
+class ReferrerTree(object):
+ """An object which gathers all referrers of an object to a given depth."""
+
+ peek_length = 40
+
+ def __init__(self, ignore=None, maxdepth=2, maxparents=10):
+ self.ignore = ignore or []
+ self.ignore.append(inspect.currentframe().f_back)
+ self.maxdepth = maxdepth
+ self.maxparents = maxparents
+
+ def ascend(self, obj, depth=1):
+ """Return a nested list containing referrers of the given object."""
+ depth += 1
+ parents = []
+
+ # Gather all referrers in one step to minimize
+ # cascading references due to repr() logic.
+ refs = gc.get_referrers(obj)
+ self.ignore.append(refs)
+ if len(refs) > self.maxparents:
+ return [("[%s referrers]" % len(refs), [])]
+
+ try:
+ ascendcode = self.ascend.__code__
+ except AttributeError:
+ ascendcode = self.ascend.im_func.func_code
+ for parent in refs:
+ if inspect.isframe(parent) and parent.f_code is ascendcode:
+ continue
+ if parent in self.ignore:
+ continue
+ if depth <= self.maxdepth:
+ parents.append((parent, self.ascend(parent, depth)))
+ else:
+ parents.append((parent, []))
+
+ return parents
+
+ def peek(self, s):
+ """Return s, restricted to a sane length."""
+ if len(s) > (self.peek_length + 3):
+ half = self.peek_length // 2
+ return s[:half] + '...' + s[-half:]
+ else:
+ return s
+
+ def _format(self, obj, descend=True):
+ """Return a string representation of a single object."""
+ if inspect.isframe(obj):
+ filename, lineno, func, context, index = inspect.getframeinfo(obj)
+ return "<frame of function '%s'>" % func
+
+ if not descend:
+ return self.peek(repr(obj))
+
+ if isinstance(obj, dict):
+ return "{" + ", ".join(["%s: %s" % (self._format(k, descend=False),
+ self._format(v, descend=False))
+ for k, v in obj.items()]) + "}"
+ elif isinstance(obj, list):
+ return "[" + ", ".join([self._format(item, descend=False)
+ for item in obj]) + "]"
+ elif isinstance(obj, tuple):
+ return "(" + ", ".join([self._format(item, descend=False)
+ for item in obj]) + ")"
+
+ r = self.peek(repr(obj))
+ if isinstance(obj, (str, int, float)):
+ return r
+ return "%s: %s" % (type(obj), r)
+
+ def format(self, tree):
+ """Return a list of string reprs from a nested list of referrers."""
+ output = []
+ def ascend(branch, depth=1):
+ for parent, grandparents in branch:
+ output.append((" " * depth) + self._format(parent))
+ if grandparents:
+ ascend(grandparents, depth + 1)
+ ascend(tree)
+ return output
+
+
+def get_instances(cls):
+ return [x for x in gc.get_objects() if isinstance(x, cls)]
+
+
+class RequestCounter(SimplePlugin):
+
+ def start(self):
+ self.count = 0
+
+ def before_request(self):
+ self.count += 1
+
+ def after_request(self):
+ self.count -=1
+request_counter = RequestCounter(cherrypy.engine)
+request_counter.subscribe()
+
+
+def get_context(obj):
+ if isinstance(obj, _cprequest.Request):
+ return "path=%s;stage=%s" % (obj.path_info, obj.stage)
+ elif isinstance(obj, _cprequest.Response):
+ return "status=%s" % obj.status
+ elif isinstance(obj, _cpwsgi.AppResponse):
+ return "PATH_INFO=%s" % obj.environ.get('PATH_INFO', '')
+ elif hasattr(obj, "tb_lineno"):
+ return "tb_lineno=%s" % obj.tb_lineno
+ return ""
+
+
+class GCRoot(object):
+ """A CherryPy page handler for testing reference leaks."""
+
+ classes = [(_cprequest.Request, 2, 2,
+ "Should be 1 in this request thread and 1 in the main thread."),
+ (_cprequest.Response, 2, 2,
+ "Should be 1 in this request thread and 1 in the main thread."),
+ (_cpwsgi.AppResponse, 1, 1,
+ "Should be 1 in this request thread only."),
+ ]
+
+ def index(self):
+ return "Hello, world!"
+ index.exposed = True
+
+ def stats(self):
+ output = ["Statistics:"]
+
+ for trial in range(10):
+ if request_counter.count > 0:
+ break
+ time.sleep(0.5)
+ else:
+ output.append("\nNot all requests closed properly.")
+
+ # gc_collect isn't perfectly synchronous, because it may
+ # break reference cycles that then take time to fully
+ # finalize. Call it thrice and hope for the best.
+ gc.collect()
+ gc.collect()
+ unreachable = gc.collect()
+ if unreachable:
+ if objgraph is not None:
+ final = objgraph.by_type('Nondestructible')
+ if final:
+ objgraph.show_backrefs(final, filename='finalizers.png')
+
+ trash = {}
+ for x in gc.garbage:
+ trash[type(x)] = trash.get(type(x), 0) + 1
+ if trash:
+ output.insert(0, "\n%s unreachable objects:" % unreachable)
+ trash = [(v, k) for k, v in trash.items()]
+ trash.sort()
+ for pair in trash:
+ output.append(" " + repr(pair))
+
+ # Check declared classes to verify uncollected instances.
+ # These don't have to be part of a cycle; they can be
+ # any objects that have unanticipated referrers that keep
+ # them from being collected.
+ allobjs = {}
+ for cls, minobj, maxobj, msg in self.classes:
+ allobjs[cls] = get_instances(cls)
+
+ for cls, minobj, maxobj, msg in self.classes:
+ objs = allobjs[cls]
+ lenobj = len(objs)
+ if lenobj < minobj or lenobj > maxobj:
+ if minobj == maxobj:
+ output.append(
+ "\nExpected %s %r references, got %s." %
+ (minobj, cls, lenobj))
+ else:
+ output.append(
+ "\nExpected %s to %s %r references, got %s." %
+ (minobj, maxobj, cls, lenobj))
+
+ for obj in objs:
+ if objgraph is not None:
+ ig = [id(objs), id(inspect.currentframe())]
+ fname = "graph_%s_%s.png" % (cls.__name__, id(obj))
+ objgraph.show_backrefs(
+ obj, extra_ignore=ig, max_depth=4, too_many=20,
+ filename=fname, extra_info=get_context)
+ output.append("\nReferrers for %s (refcount=%s):" %
+ (repr(obj), sys.getrefcount(obj)))
+ t = ReferrerTree(ignore=[objs], maxdepth=3)
+ tree = t.ascend(obj)
+ output.extend(t.format(tree))
+
+ return "\n".join(output)
+ stats.exposed = True
+
View
0  cherrypy/lib/http.py 100644 → 100755
File mode changed
View
0  cherrypy/lib/httpauth.py 100644 → 100755
File mode changed
View
51 cherrypy/lib/httputil.py 100644 → 100755
@@ -9,7 +9,7 @@
from binascii import b2a_base64
from cherrypy._cpcompat import BaseHTTPRequestHandler, HTTPDate, ntob, ntou, reversed, sorted
-from cherrypy._cpcompat import basestring, iteritems, unicodestr, unquote_qs
+from cherrypy._cpcompat import basestring, bytestr, iteritems, nativestr, unicodestr, unquote_qs
response_codes = BaseHTTPRequestHandler.responses.copy()
# From http://www.cherrypy.org/ticket/361
@@ -38,6 +38,18 @@ def urljoin(*atoms):
# Special-case the final url of "", and return "/" instead.
return url or "/"
+def urljoin_bytes(*atoms):
+ """Return the given path *atoms, joined into a single URL.
+
+ This will correctly join a SCRIPT_NAME and PATH_INFO into the
+ original URL, even if either atom is blank.
+ """
+ url = ntob("/").join([x for x in atoms if x])
+ while ntob("//") in url:
+ url = url.replace(ntob("//"), ntob("/"))
+ # Special-case the final url of "", and return "/" instead.
+ return url or ntob("/")
+
def protocol_from_http(protocol_str):
"""Return a protocol tuple from the given 'HTTP/x.y' string."""
return int(protocol_str[5]), int(protocol_str[7])
@@ -105,9 +117,15 @@ def __init__(self, value, params=None):
def __cmp__(self, other):
return cmp(self.value, other.value)
+ def __lt__(self, other):
+ return self.value < other.value
+
def __str__(self):
p = [";%s=%s" % (k, v) for k, v in iteritems(self.params)]
return "%s%s" % (self.value, "".join(p))
+
+ def __bytes__(self):
+ return ntob(self.__str__())
def __unicode__(self):
return ntou(self.__str__())
@@ -181,6 +199,12 @@ def __cmp__(self, other):
if diff == 0:
diff = cmp(str(self), str(other))
return diff
+
+ def __lt__(self, other):
+ if self.qvalue == other.qvalue:
+ return str(self) < str(other)
+ else:
+ return self.qvalue < other.qvalue
def header_elements(fieldname, fieldvalue):
@@ -199,8 +223,12 @@ def header_elements(fieldname, fieldvalue):
return list(reversed(sorted(result)))
def decode_TEXT(value):
- r"""Decode :rfc:`2047` TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> u"f\xfcr")."""
- from email.Header import decode_header
+ r"""Decode :rfc:`2047` TEXT (e.g. "=?utf-8?q?f=C3=BCr?=" -> "f\xfcr")."""
+ try:
+ # Python 3
+ from email.header import decode_header
+ except ImportError:
+ from email.Header import decode_header
atoms = decode_header(value)
decodedvalue = ""
for atom, charset in atoms:
@@ -253,6 +281,10 @@ def valid_status(status):
return code, reason, message
+# NOTE: the parse_qs functions that follow are modified version of those
+# in the python3.0 source - we need to pass through an encoding to the unquote
+# method, but the default parse_qs function doesn't allow us to. These do.
+
def _parse_qs(qs, keep_blank_values=0, strict_parsing=0, encoding='utf-8'):
"""Parse a query given as a string argument.
@@ -338,8 +370,9 @@ def __contains__(self, key):
def get(self, key, default=None):
return dict.get(self, str(key).title(), default)
- def has_key(self, key):
- return dict.has_key(self, str(key).title())
+ if hasattr({}, 'has_key'):
+ def has_key(self, key):
+ return dict.has_key(self, str(key).title())
def update(self, E):
for k in E.keys():
@@ -369,8 +402,12 @@ def pop(self, key, default):
# A CRLF is allowed in the definition of TEXT only as part of a header
# field continuation. It is expected that the folding LWS will be
# replaced with a single SP before interpretation of the TEXT value."
-header_translate_table = ''.join([chr(i) for i in xrange(256)])
-header_translate_deletechars = ''.join([chr(i) for i in xrange(32)]) + chr(127)
+if nativestr == bytestr:
+ header_translate_table = ''.join([chr(i) for i in xrange(256)])
+ header_translate_deletechars = ''.join([chr(i) for i in xrange(32)]) + chr(127)
+else:
+ header_translate_table = None
+ header_translate_deletechars = bytes(range(32)) + bytes([127])
class HeaderMap(CaseInsensitiveDict):
View
2  cherrypy/lib/jsontools.py 100644 → 100755
@@ -82,6 +82,6 @@ def json_out(content_type='application/json', debug=False, handler=json_handler)
request.handler = handler
if content_type is not None:
if debug:
- cherrypy.log('Setting Content-Type to %s' % ct, 'TOOLS.JSON_OUT')
+ cherrypy.log('Setting Content-Type to %s' % content_type, 'TOOLS.JSON_OUT')
cherrypy.serving.response.headers['Content-Type'] = content_type
View
0  cherrypy/lib/profiler.py 100644 → 100755
File mode changed
View
168 cherrypy/lib/reprconf.py 100644 → 100755
@@ -28,6 +28,20 @@
set
except NameError:
from sets import Set as set
+
+try:
+ basestring
+except NameError:
+ basestring = str
+
+try:
+ # Python 3
+ import builtins
+except ImportError:
+ # Python 2
+ import __builtin__ as builtins
+
+import operator as _operator
import sys
def as_dict(config):
@@ -195,10 +209,11 @@ def as_dict(self, raw=False, vars=None):
if section not in result:
result[section] = {}
for option in self.options(section):
- value = self.get(section, option, raw, vars)
+ value = self.get(section, option, raw=raw, vars=vars)
try:
value = unrepr(value)
- except Exception, x:
+ except Exception:
+ x = sys.exc_info()[1]
msg = ("Config error in section: %r, option: %r, "
"value: %r. Config values must be valid Python." %
(section, option, value))
@@ -216,7 +231,8 @@ def dict_from_file(self, file):
# public domain "unrepr" implementation, found on the web and then improved.
-class _Builder:
+
+class _Builder2:
def build(self, o):
m = getattr(self, 'build_' + o.__class__.__name__, None)
@@ -225,6 +241,18 @@ def build(self, o):
repr(o.__class__.__name__))
return m(o)
+ def astnode(self, s):
+ """Return a Python2 ast Node compiled from a string."""
+ try:
+ import compiler
+ except ImportError:
+ # Fallback to eval when compiler package is not available,
+ # e.g. IronPython 1.0.
+ return eval(s)
+
+ p = compiler.parse("__tempvalue__ = " + s)
+ return p.getChildren()[1].getChildren()[0].getChildren()[1]
+
def build_Subscript(self, o):
expr, flags, subs = o.getChildren()
expr = self.build(expr)
@@ -272,8 +300,7 @@ def build_Name(self, o):
# See if the Name is in builtins.
try:
- import __builtin__
- return getattr(__builtin__, name)
+ return getattr(builtins, name)
except AttributeError:
pass
@@ -282,6 +309,10 @@ def build_Name(self, o):
def build_Add(self, o):
left, right = map(self.build, o.getChildren())
return left + right
+
+ def build_Mul(self, o):
+ left, right = map(self.build, o.getChildren())
+ return left * right
def build_Getattr(self, o):
parent = self.build(o.expr)
@@ -297,25 +328,128 @@ def build_UnaryAdd(self, o):
return self.build(o.getChildren()[0])
-def _astnode(s):
- """Return a Python ast Node compiled from a string."""
- try:
- import compiler
- except ImportError:
- # Fallback to eval when compiler package is not available,
- # e.g. IronPython 1.0.
- return eval(s)
+class _Builder3:
+
+ def build(self, o):
+ m = getattr(self, 'build_' + o.__class__.__name__, None)
+ if m is None:
+ raise TypeError("unrepr does not recognize %s" %
+ repr(o.__class__.__name__))
+ return m(o)
+
+ def astnode(self, s):
+ """Return a Python3 ast Node compiled from a string."""
+ try:
+ import ast
+ except ImportError:
+ # Fallback to eval when ast package is not available,
+ # e.g. IronPython 1.0.
+ return eval(s)
+
+ p = ast.parse("__tempvalue__ = " + s)
+ return p.body[0].value
+
+ def build_Subscript(self, o):
+ return self.build(o.value)[self.build(o.slice)]
+
+ def build_Index(self, o):
+ return self.build(o.value)
+
+ def build_Call(self, o):
+ callee = self.build(o.func)
+
+ if o.args is None:
+ args = ()
+ else:
+ args = tuple([self.build(a) for a in o.args])
+
+ if o.starargs is None:
+ starargs = ()
+ else:
+ starargs = self.build(o.starargs)
+
+ if o.kwargs is None:
+ kwargs = {}
+ else:
+ kwargs = self.build(o.kwargs)
+
+ return callee(*(args + starargs), **kwargs)
+
+ def build_List(self, o):
+ return list(map(self.build, o.elts))
- p = compiler.parse("__tempvalue__ = " + s)
- return p.getChildren()[1].getChildren()[0].getChildren()[1]
+ def build_Str(self, o):
+ return o.s
+ def build_Num(self, o):
+ return o.n
+
+ def build_Dict(self, o):
+ return dict([(self.build(k), self.build(v))
+ for k, v in zip(o.keys, o.values)])
+
+ def build_Tuple(self, o):
+ return tuple(self.build_List(o))
+
+ def build_Name(self, o):
+ name = o.id
+ if name == 'None':
+ return None
+ if name == 'True':
+ return True
+ if name == 'False':
+ return False
+
+ # See if the Name is a package or module. If it is, import it.
+ try:
+ return modules(name)
+ except ImportError:
+ pass
+
+ # See if the Name is in builtins.
+ try:
+ import builtins
+ return getattr(builtins, name)
+ except AttributeError:
+ pass
+
+ raise TypeError("unrepr could not resolve the name %s" % repr(name))
+
+ def build_UnaryOp(self, o):
+ op, operand = map(self.build, [o.op, o.operand])
+ return op(operand)
+
+ def build_BinOp(self, o):
+ left, op, right = map(self.build, [o.left, o.op, o.right])
+ return op(left, right)
+
+ def build_Add(self, o):
+ return _operator.add
+
+ def build_Mult(self, o):
+ return _operator.mul
+
+ def build_USub(self, o):
+ return _operator.neg
+
+ def build_Attribute(self, o):
+ parent = self.build(o.value)
+ return getattr(parent, o.attr)
+
+ def build_NoneType(self, o):
+ return None
+
def unrepr(s):
"""Return a Python object compiled from a string."""
if not s:
return s
- obj = _astnode(s)
- return _Builder().build(obj)
+ if sys.version_info < (3, 0):
+ b = _Builder2()
+ else:
+ b = _Builder3()
+ obj = b.astnode(s)
+ return b.build(obj)
def modules(modulePath):
View
73 cherrypy/lib/sessions.py 100644 → 100755
@@ -93,7 +93,7 @@
from warnings import warn
import cherrypy
-from cherrypy._cpcompat import copyitems, pickle, random20
+from cherrypy._cpcompat import copyitems, pickle, random20, unicodestr
from cherrypy.lib import httputil
@@ -171,7 +171,15 @@ def __init__(self, id=None, **kwargs):
self.id = None
self.missing = True
self._regenerate()
-
+
+ def now(self):
+ """Generate the session specific concept of 'now'.
+
+ Other session providers can override this to use alternative,
+ possibly timezone aware, versions of 'now'.
+ """
+ return datetime.datetime.now()
+
def regenerate(self):
"""Replace the current session (with a new id)."""
self.regenerated = True
@@ -210,7 +218,7 @@ def save(self):
# accessed: no need to save it
if self.loaded:
t = datetime.timedelta(seconds = self.timeout * 60)
- expiration_time = datetime.datetime.now() + t
+ expiration_time = self.now() + t
if self.debug:
cherrypy.log('Saving with expiry %s' % expiration_time,
'TOOLS.SESSIONS')
@@ -225,7 +233,7 @@ def load(self):
"""Copy stored session data into this session instance."""
data = self._load()
# data is either None or a tuple (session_data, expiration_time)
- if data is None or data[1] < datetime.datetime.now():
+ if data is None or data[1] < self.now():
if self.debug:
cherrypy.log('Expired session, flushing data', 'TOOLS.SESSIONS')
self._data = {}
@@ -277,10 +285,11 @@ def __contains__(self, key):
if not self.loaded: self.load()
return key in self._data
- def has_key(self, key):
- """D.has_key(k) -> True if D has a key k, else False."""
- if not self.loaded: self.load()
- return key in self._data
+ if hasattr({}, 'has_key'):
+ def has_key(self, key):
+ """D.has_key(k) -> True if D has a key k, else False."""
+ if not self.loaded: self.load()
+ return key in self._data
def get(self, key, default=None):
"""D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
@@ -326,7 +335,7 @@ class RamSession(Session):
def clean_up(self):
"""Clean up expired sessions."""
- now = datetime.datetime.now()
+ now = self.now()
for id, (data, expiration_time) in copyitems(self.cache):
if expiration_time <= now:
try:
@@ -337,6 +346,11 @@ def clean_up(self):
del self.locks[id]
except KeyError:
pass
+
+ # added to remove obsolete lock objects
+ for id in list(self.locks):
+ if id not in self.cache:
+ self.locks.pop(id, None)
def _exists(self):
return self.id in self.cache
@@ -467,7 +481,7 @@ def release_lock(self, path=None):
def clean_up(self):
"""Clean up expired sessions."""
- now = datetime.datetime.now()
+ now = self.now()
# Iterate over all session files in self.storage_path
for fname in os.listdir(self.storage_path):
if (fname.startswith(self.SESSION_PREFIX)
@@ -575,7 +589,7 @@ def release_lock(self):
def clean_up(self):
"""Clean up expired sessions."""
self.cursor.execute('delete from session where expiration_time < %s',
- (datetime.datetime.now(),))
+ (self.now(),))
class MemcachedSession(Session):
@@ -602,6 +616,19 @@ def setup(cls, **kwargs):
cls.cache = memcache.Client(cls.servers)
setup = classmethod(setup)
+ def _get_id(self):
+ return self._id
+ def _set_id(self, value):
+ # This encode() call is where we differ from the superclass.
+ # Memcache keys MUST be byte strings, not unicode.
+ if isinstance(value, unicodestr):
+ value = value.encode('utf-8')
+
+ self._id = value
+ for o in self.id_observers:
+ o(value)
+ id = property(_get_id, _set_id, doc="The current session ID.")
+
def _exists(self):
self.mc_lock.acquire()
try:
@@ -683,12 +710,12 @@ def close():
def init(storage_type='ram', path=None, path_header=None, name='session_id',
timeout=60, domain=None, secure=False, clean_freq=5,
- persistent=True, debug=False, **kwargs):
+ persistent=True, httponly=False, debug=False, **kwargs):
"""Initialize session object (using cookies).
storage_type
- One of 'ram', 'file', 'postgresql'. This will be used
- to look up the corresponding class in cherrypy.lib.sessions
+ One of 'ram', 'file', 'postgresql', 'memcached'. This will be
+ used to look up the corresponding class in cherrypy.lib.sessions
globals. For example, 'file' will use the FileSession class.
path
@@ -722,6 +749,10 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
and the cookie will be a "session cookie" which expires when the
browser is closed.
+ httponly
+ If False (the default) the cookie 'httponly' value will not be set.
+ If True, the cookie 'httponly' value will be set (to 1).
+
Any additional kwargs will be bound to the new Session instance,
and may be specific to the storage type. See the subclass of Session
you're using for more information.
@@ -772,11 +803,12 @@ def update_cookie(id):
# and http://support.mozilla.com/en-US/kb/Cookies
cookie_timeout = None
set_response_cookie(path=path, path_header=path_header, name=name,
- timeout=cookie_timeout, domain=domain, secure=secure)
+ timeout=cookie_timeout, domain=domain, secure=secure,
+ httponly=httponly)
def set_response_cookie(path=None, path_header=None, name='session_id',
- timeout=60, domain=None, secure=False):
+ timeout=60, domain=None, secure=False, httponly=False):
"""Set a response cookie for the client.
path
@@ -801,6 +833,10 @@ def set_response_cookie(path=None, path_header=None, name='session_id',
if False (the default) the cookie 'secure' value will not
be set. If True, the cookie 'secure' value will be set (to 1).