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


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)."""
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,
0 cherrypy/_cpnative_server.py 100644 → 100755
Empty file.
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):
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:
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."""
0 cherrypy/_cpthreadinglocal.py 100644 → 100755
Empty file.
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", {})
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,16 +125,16 @@ 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

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)

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']
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)

BIN +1.37 KB cherrypy/favicon.ico
Binary file not shown.
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)"""
0 cherrypy/lib/auth.py 100644 → 100755
Empty file.
0 cherrypy/lib/auth_basic.py 100644 → 100755
Empty file.
0 cherrypy/lib/auth_digest.py 100644 → 100755
Empty file.
0 cherrypy/lib/caching.py 100644 → 100755
Empty file.
0 cherrypy/lib/covercp.py 100644 → 100755
Empty file.
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']
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):
0 cherrypy/lib/encoding.py 100644 → 100755
Empty file.
@@ -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

0 cherrypy/lib/http.py 100644 → 100755
Empty file.
0 cherrypy/lib/httpauth.py 100644 → 100755
Empty file.
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):
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

0 cherrypy/lib/profiler.py 100644 → 100755
Empty file.
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):
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).
httponly
If False (the default) the cookie 'httponly' value will not be set.
If True, the cookie 'httponly' value will be set (to 1).
"""
# Set response cookie
cookie = cherrypy.serving.response.cookie
@@ -820,7 +856,10 @@ def set_response_cookie(path=None, path_header=None, name='session_id',
cookie[name]['domain'] = domain
if secure:
cookie[name]['secure'] = 1

if httponly:
if not cookie[name].isReservedKey('httponly'):
raise ValueError("The httponly cookie token is not supported.")
cookie[name]['httponly'] = 1

def expire():
"""Expire the current session cookie."""
13 cherrypy/lib/static.py 100644 → 100755
@@ -1,3 +1,7 @@
try:
from io import UnsupportedOperation
except ImportError:
UnsupportedOperation = object()
import logging
import mimetypes
mimetypes.init()
@@ -115,6 +119,8 @@ def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
if debug:
cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC')
content_length = None
except UnsupportedOperation:
content_length = None
else:
# Set the Last-Modified response header, so that
# modified-since validation code can work.
@@ -174,7 +180,12 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
else:
# Return a multipart/byteranges response.
response.status = "206 Partial Content"
from mimetools import choose_boundary
try:
# Python 3
from email.generator import _make_boundary as choose_boundary
except ImportError:
# Python 2
from mimetools import choose_boundary
boundary = choose_boundary()
ct = "multipart/byteranges; boundary=%s" % boundary
response.headers['Content-Type'] = ct
@@ -1,13 +1,19 @@
import sys

import cherrypy
from cherrypy._cpcompat import ntob

def get_xmlrpclib():
try:
import xmlrpc.client as x
except ImportError:
import xmlrpclib as x
return x

def process_body():
"""Return (params, method) from request body."""
try:
import xmlrpclib
return xmlrpclib.loads(cherrypy.request.body.read())
return get_xmlrpclib().loads(cherrypy.request.body.read())
except Exception:
return ('ERROR PARAMS', ), 'ERRORMETHOD'

@@ -29,21 +35,21 @@ def _set_response(body):
# as a "Protocol Error", we'll just return 200 every time.
response = cherrypy.response
response.status = '200 OK'
response.body = body
response.body = ntob(body, 'utf-8')
response.headers['Content-Type'] = 'text/xml'
response.headers['Content-Length'] = len(body)


def respond(body, encoding='utf-8', allow_none=0):
from xmlrpclib import Fault, dumps
if not isinstance(body, Fault):
xmlrpclib = get_xmlrpclib()
if not isinstance(body, xmlrpclib.Fault):
body = (body,)
_set_response(dumps(body, methodresponse=1,
encoding=encoding,
allow_none=allow_none))
_set_response(xmlrpclib.dumps(body, methodresponse=1,
encoding=encoding,
allow_none=allow_none))

def on_error(*args, **kwargs):
body = str(sys.exc_info()[1])
from xmlrpclib import Fault, dumps
_set_response(dumps(Fault(1, body)))
xmlrpclib = get_xmlrpclib()
_set_response(xmlrpclib.dumps(xmlrpclib.Fault(1, body)))

0 cherrypy/process/__init__.py 100644 → 100755
Empty file.
12 cherrypy/process/plugins.py 100644 → 100755
@@ -453,13 +453,14 @@ class BackgroundTask(threading.Thread):
it won't delay stopping the whole process.
"""

def __init__(self, interval, function, args=[], kwargs={}):
def __init__(self, interval, function, args=[], kwargs={}, bus=None):
threading.Thread.__init__(self)
self.interval = interval
self.function = function
self.args = args
self.kwargs = kwargs
self.running = False
self.bus = bus

def cancel(self):
self.running = False
@@ -473,8 +474,9 @@ def run(self):
try:
self.function(*self.args, **self.kwargs)
except Exception:
self.bus.log("Error in background task thread function %r." %
self.function, level=40, traceback=True)
if self.bus:
self.bus.log("Error in background task thread function %r."
% self.function, level=40, traceback=True)
# Quit on first error to avoid massive logs.
raise

@@ -506,8 +508,8 @@ def start(self):
if self.frequency > 0:
threadname = self.name or self.__class__.__name__
if self.thread is None:
self.thread = BackgroundTask(self.frequency, self.callback)
self.thread.bus = self.bus
self.thread = BackgroundTask(self.frequency, self.callback,
bus = self.bus)
self.thread.setName(threadname)
self.thread.start()
self.bus.log("Started monitor thread %r." % threadname)
21 cherrypy/process/servers.py 100644 → 100755
@@ -385,34 +385,43 @@ def check_port(host, port, timeout=1.0):
if s:
s.close()

def wait_for_free_port(host, port):

# Feel free to increase these defaults on slow systems:
free_port_timeout = 0.1
occupied_port_timeout = 1.0

def wait_for_free_port(host, port, timeout=None):
"""Wait for the specified port to become free (drop requests)."""
if not host:
raise ValueError("Host values of '' or None are not allowed.")
if timeout is None:
timeout = free_port_timeout

for trial in range(50):
try:
# we are expecting a free port, so reduce the timeout
check_port(host, port, timeout=0.1)
check_port(host, port, timeout=timeout)
except IOError:
# Give the old server thread time to free the port.
time.sleep(0.1)
time.sleep(timeout)
else:
return

raise IOError("Port %r not free on %r" % (port, host))

def wait_for_occupied_port(host, port):
def wait_for_occupied_port(host, port, timeout=None):
"""Wait for the specified port to become active (receive requests)."""
if not host:
raise ValueError("Host values of '' or None are not allowed.")
if timeout is None:
timeout = occupied_port_timeout

for trial in range(50):
try:
check_port(host, port)
check_port(host, port, timeout=timeout)
except IOError:
return
else:
time.sleep(.1)
time.sleep(timeout)

raise IOError("Port %r not bound on %r" % (port, host))
0 cherrypy/process/win32.py 100644 → 100755
Empty file.
57 cherrypy/process/wspbus.py 100644 → 100755
@@ -90,20 +90,21 @@ def __init__(self, *args, **kwargs):

def handle_exception(self):
"""Append the current exception to self."""
self._exceptions.append(sys.exc_info())
self._exceptions.append(sys.exc_info()[1])

def get_instances(self):
"""Return a list of seen exception instances."""
return [instance for cls, instance, traceback in self._exceptions]
return self._exceptions[:]

def __str__(self):
exception_strings = map(repr, self.get_instances())
return self.delimiter.join(exception_strings)

__repr__ = __str__

def __nonzero__(self):
def __bool__(self):
return bool(self._exceptions)
__nonzero__ = __bool__

# Use a flag to indicate the state of the bus.
class _StateEnum(object):
@@ -124,6 +125,17 @@ def __setattr__(self, key, value):
states.EXITING = states.State()


try:
import fcntl
except ImportError:
max_files = 0
else:
try:
max_files = os.sysconf('SC_OPEN_MAX')
except AttributeError:
max_files = 1024


class Bus(object):
"""Process state-machine and messenger for HTTP site deployment.
@@ -137,6 +149,7 @@ class Bus(object):
states = states
state = states.STOPPED
execv = False
max_cloexec_files = max_files

def __init__(self):
self.execv = False
@@ -173,13 +186,19 @@ def publish(self, channel, *args, **kwargs):

items = [(self._priorities[(channel, listener)], listener)
for listener in self.listeners[channel]]
items.sort()
try:
items.sort(key=lambda item: item[0])
except TypeError:
# Python 2.3 had no 'key' arg, but that doesn't matter
# since it could sort dissimilar types just fine.
items.sort()
for priority, listener in items:
try:
output.append(listener(*args, **kwargs))
except KeyboardInterrupt:
raise
except SystemExit, e:
except SystemExit:
e = sys.exc_info()[1]
# If we have previous errors ensure the exit code is non-zero
if exc and e.code == 0:
e.code = 1
@@ -221,13 +240,14 @@ def start(self):
except:
self.log("Shutting down due to error in start listener:",
level=40, traceback=True)
e_info = sys.exc_info()
e_info = sys.exc_info()[1]
try:
self.exit()
except:
# Any stop/exit errors will be logged inside publish().
pass
raise e_info[0], e_info[1], e_info[2]
# Re-raise the original error
raise e_info

def exit(self):
"""Stop all services and prepare to exit the process."""
@@ -354,8 +374,28 @@ def _do_execv(self):
args = ['"%s"' % arg for arg in args]

os.chdir(_startup_cwd)
if self.max_cloexec_files:
self._set_cloexec()
os.execv(sys.executable, args)

def _set_cloexec(self):
"""Set the CLOEXEC flag on all open files (except stdin/out/err).
If self.max_cloexec_files is an integer (the default), then on
platforms which support it, it represents the max open files setting
for the operating system. This function will be called just before
the process is restarted via os.execv() to prevent open files
from persisting into the new process.
Set self.max_cloexec_files to 0 to disable this behavior.
"""
for fd in range(3, self.max_cloexec_files): # skip stdin/out/err
try:
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
except IOError:
continue
fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)

def stop(self):
"""Stop all services."""
self.state = states.STOPPING
@@ -386,8 +426,7 @@ def _callback(func, *a, **kw):
def log(self, msg="", level=20, traceback=False):
"""Log the given message. Append the last traceback if requested."""
if traceback:
exc = sys.exc_info()
msg += "\n" + "".join(_traceback.format_exception(*exc))
msg += "\n" + "".join(_traceback.format_exception(*sys.exc_info()))
self.publish('log', msg, level)

bus = Bus()
2,227 cherrypy/wsgiserver/__init__.py 100644 → 100755

Large diffs are not rendered by default.

25 cherrypy/wsgiserver/ssl_builtin.py 100644 → 100755
@@ -11,6 +11,16 @@
except ImportError:
ssl = None

try:
from _pyio import DEFAULT_BUFFER_SIZE
except ImportError:
try:
from io import DEFAULT_BUFFER_SIZE
except ImportError:
DEFAULT_BUFFER_SIZE = -1

import sys

from cherrypy import wsgiserver


@@ -40,7 +50,8 @@ def wrap(self, sock):
s = ssl.wrap_socket(sock, do_handshake_on_connect=True,
server_side=True, certfile=self.certificate,
keyfile=self.private_key, ssl_version=ssl.PROTOCOL_SSLv23)
except ssl.SSLError, e:
except ssl.SSLError:
e = sys.exc_info()[1]
if e.errno == ssl.SSL_ERROR_EOF:
# This is almost certainly due to the cherrypy engine
# 'pinging' the socket to assert it's connectable;
@@ -50,6 +61,10 @@ def wrap(self, sock):
if e.args[1].endswith('http request'):
# The client is speaking HTTP to an HTTPS server.
raise wsgiserver.NoSSLError
elif e.args[1].endswith('unknown protocol'):
# The client is speaking some non-HTTP protocol.
# Drop the conn.
return None, {}
raise
return s, self.get_environ(s)

@@ -67,6 +82,10 @@ def get_environ(self, sock):
}
return ssl_environ

def makefile(self, sock, mode='r', bufsize=-1):
return wsgiserver.CP_fileobject(sock, mode, bufsize)
if sys.version_info >= (3, 0):
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
return wsgiserver.CP_makefile(sock, mode, bufsize)
else:
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
return wsgiserver.CP_fileobject(sock, mode, bufsize)

Empty file.

Large diffs are not rendered by default.

Large diffs are not rendered by default.