Browse Source

CherryPy 8.1.2 - Update and set version

pull/701/head
Safihre 9 years ago
parent
commit
692ed8fce8
  1. 4
      SABnzbd.py
  2. 50
      cherrypy/LICENSE.txt
  3. 3
      cherrypy/VERSION.txt
  4. 58
      cherrypy/__init__.py
  5. 146
      cherrypy/_cpchecker.py
  6. 115
      cherrypy/_cpcompat.py
  7. 1544
      cherrypy/_cpcompat_subprocess.py
  8. 80
      cherrypy/_cpconfig.py
  9. 84
      cherrypy/_cpdispatch.py
  10. 74
      cherrypy/_cperror.py
  11. 54
      cherrypy/_cplogging.py
  12. 68
      cherrypy/_cpmodpy.py
  13. 20
      cherrypy/_cpnative_server.py
  14. 80
      cherrypy/_cpreqbody.py
  15. 113
      cherrypy/_cprequest.py
  16. 26
      cherrypy/_cpserver.py
  17. 241
      cherrypy/_cpthreadinglocal.py
  18. 73
      cherrypy/_cptools.py
  19. 60
      cherrypy/_cptree.py
  20. 166
      cherrypy/_cpwsgi.py
  21. 2
      cherrypy/_cpwsgi_server.py
  22. 18
      cherrypy/_helper.py
  23. 46
      cherrypy/daemon.py
  24. 22
      cherrypy/lib/__init__.py
  25. 16
      cherrypy/lib/auth.py
  26. 18
      cherrypy/lib/auth_basic.py
  27. 51
      cherrypy/lib/auth_digest.py
  28. 26
      cherrypy/lib/caching.py
  29. 45
      cherrypy/lib/covercp.py
  30. 27
      cherrypy/lib/cpstats.py
  31. 92
      cherrypy/lib/cptools.py
  32. 48
      cherrypy/lib/encoding.py
  33. 57
      cherrypy/lib/gctools.py
  34. 6
      cherrypy/lib/http.py
  35. 129
      cherrypy/lib/httpauth.py
  36. 104
      cherrypy/lib/httputil.py
  37. 10
      cherrypy/lib/jsontools.py
  38. 289
      cherrypy/lib/lockfile.py
  39. 6
      cherrypy/lib/locking.py
  40. 59
      cherrypy/lib/profiler.py
  41. 45
      cherrypy/lib/reprconf.py
  42. 103
      cherrypy/lib/sessions.py
  43. 56
      cherrypy/lib/static.py
  44. 4
      cherrypy/process/__init__.py
  45. 84
      cherrypy/process/plugins.py
  46. 65
      cherrypy/process/servers.py
  47. 12
      cherrypy/process/win32.py
  48. 110
      cherrypy/process/wspbus.py
  49. 2571
      cherrypy/wsgiserver/__init__.py
  50. 27
      cherrypy/wsgiserver/ssl_builtin.py
  51. 28
      cherrypy/wsgiserver/ssl_pyopenssl.py
  52. 16
      cherrypy/wsgiserver/test_wsgiserver.py
  53. 2483
      cherrypy/wsgiserver/wsgiserver2.py
  54. 2198
      cherrypy/wsgiserver/wsgiserver3.py

4
SABnzbd.py

@ -52,8 +52,8 @@ except:
sys.exit(1)
import cherrypy
if [int(n) for n in cherrypy.__version__.split('.')] < [6, 0, 2]:
print 'Sorry, requires Python module Cherrypy 6.0.2+ (use the included version)'
if [int(n) for n in cherrypy.__version__.split('.')] < [8, 1, 2]:
print 'Sorry, requires Python module Cherrypy 8.1.0+ (use the included version)'
sys.exit(1)
from cherrypy import _cpserver

50
cherrypy/LICENSE.txt

@ -1,25 +1,25 @@
Copyright (c) 2004-2016, 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.
Copyright (c) 2004-2016, 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.

3
cherrypy/VERSION.txt

@ -1,4 +1,5 @@
CherryPy 6.0.2 Official distribution: https://pypi.python.org/packages/source/C/CherryPy/CherryPy-6.0.2.tar.gz
CherryPy 8.1.0
Official distribution: https://github.com/cherrypy/cherrypy/releases
The folders 'tutorial', 'test' and 'scaffold' have been removed.
This file has been added.

58
cherrypy/__init__.py

@ -61,23 +61,26 @@ try:
except ImportError:
pass
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError
from threading import local as _local
from cherrypy import _cpdispatch as dispatch
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect # noqa
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError # noqa
from cherrypy import _cplogging
from cherrypy import _cpdispatch as dispatch # noqa
from cherrypy import _cptools
tools = _cptools.default_toolbox
Tool = _cptools.Tool
from cherrypy._cptools import default_toolbox as tools, Tool
from cherrypy import _cprequest
from cherrypy.lib import httputil as _httputil
from cherrypy import _cptree
tree = _cptree.Tree()
from cherrypy._cptree import Application
from cherrypy import _cpwsgi as wsgi
from cherrypy._cptree import Application # noqa
from cherrypy import _cpwsgi as wsgi # noqa
from cherrypy import _cpserver
from cherrypy import process
try:
from cherrypy.process import win32
@ -88,11 +91,11 @@ except ImportError:
engine = process.bus
try:
__version__ = pkg_resources.require('cherrypy')[0].version
except Exception:
__version__ = 'unknown'
__version__ = '6.0.2'
tree = _cptree.Tree()
__version__ = '8.1.2'
# Timeout monitor. We add two channels to the engine
# to which cherrypy.Application will publish.
@ -141,20 +144,19 @@ class _HandleSignalsPlugin(object):
def subscribe(self):
"""Add the handlers based on the platform"""
if hasattr(self.bus, "signal_handler"):
if hasattr(self.bus, 'signal_handler'):
self.bus.signal_handler.subscribe()
if hasattr(self.bus, "console_control_handler"):
if hasattr(self.bus, 'console_control_handler'):
self.bus.console_control_handler.subscribe()
engine.signals = _HandleSignalsPlugin(engine)
from cherrypy import _cpserver
server = _cpserver.Server()
server.subscribe()
def quickstart(root=None, script_name="", config=None):
def quickstart(root=None, script_name='', config=None):
"""Mount the given root, start the builtin server (and engine), then block.
root: an instance of a "controller class" (a collection of page handler
@ -181,9 +183,6 @@ def quickstart(root=None, script_name="", config=None):
engine.block()
from cherrypy._cpcompat import threadlocal as _local
class _Serving(_local):
"""An interface for registering request and response objects.
@ -196,8 +195,8 @@ class _Serving(_local):
thread-safe way.
"""
request = _cprequest.Request(_httputil.Host("127.0.0.1", 80),
_httputil.Host("127.0.0.1", 1111))
request = _cprequest.Request(_httputil.Host('127.0.0.1', 80),
_httputil.Host('127.0.0.1', 1111))
"""
The request object for the current thread. In the main thread,
and any threads which are not receiving HTTP requests, this is None."""
@ -230,7 +229,7 @@ class _ThreadLocalProxy(object):
return getattr(child, name)
def __setattr__(self, name, value):
if name in ("__attrname__", ):
if name in ('__attrname__', ):
object.__setattr__(self, name, value)
else:
child = getattr(serving, self.__attrname__)
@ -306,9 +305,6 @@ except ImportError:
pass
from cherrypy import _cplogging
class _GlobalLogManager(_cplogging.LogManager):
"""A site-wide LogManager; routes to app.log or global log as appropriate.
@ -352,10 +348,10 @@ def _buslog(msg, level):
log.error(msg, 'ENGINE', severity=level)
engine.subscribe('log', _buslog)
from cherrypy._helper import expose, popargs, url
from cherrypy._helper import expose, popargs, url # noqa
# import _cpconfig last so it can reference other top-level objects
from cherrypy import _cpconfig
from cherrypy import _cpconfig # noqa
# Use _global_conf_alias so quickstart can use 'config' as an arg
# without shadowing cherrypy.config.
config = _global_conf_alias = _cpconfig.Config()
@ -365,11 +361,11 @@ config.defaults = {
'tools.trailing_slash.on': True,
'tools.encode.on': True
}
config.namespaces["log"] = lambda k, v: setattr(log, k, v)
config.namespaces["checker"] = lambda k, v: setattr(checker, k, v)
config.namespaces['log'] = lambda k, v: setattr(log, k, v)
config.namespaces['checker'] = lambda k, v: setattr(checker, k, v)
# Must reset to get our defaults applied.
config.reset()
from cherrypy import _cpchecker
from cherrypy import _cpchecker # noqa
checker = _cpchecker.Checker()
engine.subscribe('start', checker)

146
cherrypy/_cpchecker.py

@ -33,7 +33,7 @@ class Checker(object):
warnings.formatwarning = self.formatwarning
try:
for name in dir(self):
if name.startswith("check_"):
if name.startswith('check_'):
method = getattr(self, name)
if method and hasattr(method, '__call__'):
method()
@ -42,7 +42,7 @@ class Checker(object):
def formatwarning(self, message, category, filename, lineno, line=None):
"""Function to format a warning."""
return "CherryPy Checker:\n%s\n\n" % message
return 'CherryPy Checker:\n%s\n\n' % message
# This value should be set inside _cpconfig.
global_config_contained_paths = False
@ -57,13 +57,13 @@ class Checker(object):
continue
if sn == '':
continue
sn_atoms = sn.strip("/").split("/")
sn_atoms = sn.strip('/').split('/')
for key in app.config.keys():
key_atoms = key.strip("/").split("/")
key_atoms = key.strip('/').split('/')
if key_atoms[:len(sn_atoms)] == sn_atoms:
warnings.warn(
"The application mounted at %r has config "
"entries that start with its script name: %r" % (sn,
'The application mounted at %r has config '
'entries that start with its script name: %r' % (sn,
key))
def check_site_config_entries_in_app_config(self):
@ -76,17 +76,17 @@ class Checker(object):
for section, entries in iteritems(app.config):
if section.startswith('/'):
for key, value in iteritems(entries):
for n in ("engine.", "server.", "tree.", "checker."):
for n in ('engine.', 'server.', 'tree.', 'checker.'):
if key.startswith(n):
msg.append("[%s] %s = %s" %
msg.append('[%s] %s = %s' %
(section, key, value))
if msg:
msg.insert(0,
"The application mounted at %r contains the "
"following config entries, which are only allowed "
"in site-wide config. Move them to a [global] "
"section and pass them to cherrypy.config.update() "
"instead of tree.mount()." % sn)
'The application mounted at %r contains the '
'following config entries, which are only allowed '
'in site-wide config. Move them to a [global] '
'section and pass them to cherrypy.config.update() '
'instead of tree.mount().' % sn)
warnings.warn(os.linesep.join(msg))
def check_skipped_app_config(self):
@ -95,13 +95,13 @@ class Checker(object):
if not isinstance(app, cherrypy.Application):
continue
if not app.config:
msg = "The Application mounted at %r has an empty config." % sn
msg = 'The Application mounted at %r has an empty config.' % sn
if self.global_config_contained_paths:
msg += (" It looks like the config you passed to "
"cherrypy.config.update() contains application-"
"specific sections. You must explicitly pass "
"application config via "
"cherrypy.tree.mount(..., config=app_config)")
msg += (' It looks like the config you passed to '
'cherrypy.config.update() contains application-'
'specific sections. You must explicitly pass '
'application config via '
'cherrypy.tree.mount(..., config=app_config)')
warnings.warn(msg)
return
@ -115,12 +115,12 @@ class Checker(object):
if not app.config:
continue
for key in app.config.keys():
if key.startswith("[") or key.endswith("]"):
if key.startswith('[') or key.endswith(']'):
warnings.warn(
"The application mounted at %r has config "
"section names with extraneous brackets: %r. "
"Config *files* need brackets; config *dicts* "
"(e.g. passed to tree.mount) do not." % (sn, key))
'The application mounted at %r has config '
'section names with extraneous brackets: %r. '
'Config *files* need brackets; config *dicts* '
'(e.g. passed to tree.mount) do not.' % (sn, key))
def check_static_paths(self):
"""Check Application config for incorrect static paths."""
@ -132,47 +132,47 @@ class Checker(object):
request.app = app
for section in app.config:
# get_resource will populate request.config
request.get_resource(section + "/dummy.html")
request.get_resource(section + '/dummy.html')
conf = request.config.get
if conf("tools.staticdir.on", False):
msg = ""
root = conf("tools.staticdir.root")
dir = conf("tools.staticdir.dir")
if conf('tools.staticdir.on', False):
msg = ''
root = conf('tools.staticdir.root')
dir = conf('tools.staticdir.dir')
if dir is None:
msg = "tools.staticdir.dir is not set."
msg = 'tools.staticdir.dir is not set.'
else:
fulldir = ""
fulldir = ''
if os.path.isabs(dir):
fulldir = dir
if root:
msg = ("dir is an absolute path, even "
"though a root is provided.")
msg = ('dir is an absolute path, even '
'though a root is provided.')
testdir = os.path.join(root, dir[1:])
if os.path.exists(testdir):
msg += (
"\nIf you meant to serve the "
"filesystem folder at %r, remove the "
"leading slash from dir." % (testdir,))
'\nIf you meant to serve the '
'filesystem folder at %r, remove the '
'leading slash from dir.' % (testdir,))
else:
if not root:
msg = (
"dir is a relative path and "
"no root provided.")
'dir is a relative path and '
'no root provided.')
else:
fulldir = os.path.join(root, dir)
if not os.path.isabs(fulldir):
msg = ("%r is not an absolute path." % (
msg = ('%r is not an absolute path.' % (
fulldir,))
if fulldir and not os.path.exists(fulldir):
if msg:
msg += "\n"
msg += ("%r (root + dir) is not an existing "
"filesystem path." % fulldir)
msg += '\n'
msg += ('%r (root + dir) is not an existing '
'filesystem path.' % fulldir)
if msg:
warnings.warn("%s\nsection: [%s]\nroot: %r\ndir: %r"
warnings.warn('%s\nsection: [%s]\nroot: %r\ndir: %r'
% (msg, section, root, dir))
# -------------------------- Compatibility -------------------------- #
@ -198,19 +198,19 @@ class Checker(object):
if isinstance(conf, dict):
for k, v in conf.items():
if k in self.obsolete:
warnings.warn("%r is obsolete. Use %r instead.\n"
"section: [%s]" %
warnings.warn('%r is obsolete. Use %r instead.\n'
'section: [%s]' %
(k, self.obsolete[k], section))
elif k in self.deprecated:
warnings.warn("%r is deprecated. Use %r instead.\n"
"section: [%s]" %
warnings.warn('%r is deprecated. Use %r instead.\n'
'section: [%s]' %
(k, self.deprecated[k], section))
else:
if section in self.obsolete:
warnings.warn("%r is obsolete. Use %r instead."
warnings.warn('%r is obsolete. Use %r instead.'
% (section, self.obsolete[section]))
elif section in self.deprecated:
warnings.warn("%r is deprecated. Use %r instead."
warnings.warn('%r is deprecated. Use %r instead.'
% (section, self.deprecated[section]))
def check_compatibility(self):
@ -225,7 +225,7 @@ class Checker(object):
extra_config_namespaces = []
def _known_ns(self, app):
ns = ["wsgi"]
ns = ['wsgi']
ns.extend(copykeys(app.toolboxes))
ns.extend(copykeys(app.namespaces))
ns.extend(copykeys(app.request_class.namespaces))
@ -233,32 +233,32 @@ class Checker(object):
ns += self.extra_config_namespaces
for section, conf in app.config.items():
is_path_section = section.startswith("/")
is_path_section = section.startswith('/')
if is_path_section and isinstance(conf, dict):
for k, v in conf.items():
atoms = k.split(".")
atoms = k.split('.')
if len(atoms) > 1:
if atoms[0] not in ns:
# Spit out a special warning if a known
# namespace is preceded by "cherrypy."
if atoms[0] == "cherrypy" and atoms[1] in ns:
if atoms[0] == 'cherrypy' and atoms[1] in ns:
msg = (
"The config entry %r is invalid; "
"try %r instead.\nsection: [%s]"
% (k, ".".join(atoms[1:]), section))
'The config entry %r is invalid; '
'try %r instead.\nsection: [%s]'
% (k, '.'.join(atoms[1:]), section))
else:
msg = (
"The config entry %r is invalid, "
"because the %r config namespace "
"is unknown.\n"
"section: [%s]" % (k, atoms[0], section))
'The config entry %r is invalid, '
'because the %r config namespace '
'is unknown.\n'
'section: [%s]' % (k, atoms[0], section))
warnings.warn(msg)
elif atoms[0] == "tools":
elif atoms[0] == 'tools':
if atoms[1] not in dir(cherrypy.tools):
msg = (
"The config entry %r may be invalid, "
"because the %r tool was not found.\n"
"section: [%s]" % (k, atoms[1], section))
'The config entry %r may be invalid, '
'because the %r tool was not found.\n'
'section: [%s]' % (k, atoms[1], section))
warnings.warn(msg)
def check_config_namespaces(self):
@ -282,17 +282,17 @@ class Checker(object):
continue
vtype = type(getattr(obj, name, None))
if vtype in b:
self.known_config_types[namespace + "." + name] = vtype
self.known_config_types[namespace + '.' + name] = vtype
traverse(cherrypy.request, "request")
traverse(cherrypy.response, "response")
traverse(cherrypy.server, "server")
traverse(cherrypy.engine, "engine")
traverse(cherrypy.log, "log")
traverse(cherrypy.request, 'request')
traverse(cherrypy.response, 'response')
traverse(cherrypy.server, 'server')
traverse(cherrypy.engine, 'engine')
traverse(cherrypy.log, 'log')
def _known_types(self, config):
msg = ("The config entry %r in section %r is of type %r, "
"which does not match the expected type %r.")
msg = ('The config entry %r in section %r is of type %r, '
'which does not match the expected type %r.')
for section, conf in config.items():
if isinstance(conf, dict):
@ -326,7 +326,7 @@ class Checker(object):
for k, v in cherrypy.config.items():
if k == 'server.socket_host' and v == 'localhost':
warnings.warn("The use of 'localhost' as a socket host can "
"cause problems on newer systems, since "
'cause problems on newer systems, since '
"'localhost' can map to either an IPv4 or an "
"IPv6 address. You should use '127.0.0.1' "
"or '[::1]' instead.")

115
cherrypy/_cpcompat.py

@ -1,6 +1,6 @@
"""Compatibility code for using CherryPy with various versions of Python.
CherryPy 3.2 is compatible with Python versions 2.3+. This module provides a
CherryPy 3.2 is compatible with Python versions 2.6+. This module provides a
useful abstraction over the differences between Python versions, sometimes by
preferring a newer idiom, sometimes an older one, and sometimes a custom one.
@ -15,6 +15,8 @@ specifically with bytes, and a 'StringIO' name for dealing with native strings.
It also provides a 'base64_decode' function with native strings as input and
output.
"""
import binascii
import os
import re
import sys
@ -23,8 +25,6 @@ import threading
import six
if six.PY3:
basestring = (bytes, str)
def ntob(n, encoding='ISO-8859-1'):
"""Return the given native string as a byte string in the given
encoding.
@ -49,8 +49,6 @@ if six.PY3:
return n
else:
# Python 2
basestring = basestring
def ntob(n, encoding='ISO-8859-1'):
"""Return the given native string as a byte string in the given
encoding.
@ -90,7 +88,7 @@ else:
def assert_native(n):
if not isinstance(n, str):
raise TypeError("n must be a native str (got %s)" % type(n).__name__)
raise TypeError('n must be a native str (got %s)' % type(n).__name__)
try:
# Python 3.1+
@ -140,16 +138,11 @@ try:
from urllib.request import parse_http_list, parse_keqv_list
except ImportError:
# Python 2
from urlparse import urljoin
from urllib import urlencode, urlopen
from urllib import quote, quote_plus
from urllib import unquote
from urllib2 import parse_http_list, parse_keqv_list
try:
from threading import local as threadlocal
except ImportError:
from cherrypy._cpthreadinglocal import local as threadlocal
from urlparse import urljoin # noqa
from urllib import urlencode, urlopen # noqa
from urllib import quote, quote_plus # noqa
from urllib import unquote # noqa
from urllib2 import parse_http_list, parse_keqv_list # noqa
try:
dict.iteritems
@ -186,7 +179,7 @@ try:
import builtins
except ImportError:
# Python 2
import __builtin__ as builtins
import __builtin__ as builtins # noqa
try:
# Python 2. We try Python 2 first clients on Python 2
@ -197,10 +190,10 @@ try:
from BaseHTTPServer import BaseHTTPRequestHandler
except ImportError:
# Python 3
from http.cookies import SimpleCookie, CookieError
from http.client import BadStatusLine, HTTPConnection, IncompleteRead
from http.client import NotConnected
from http.server import BaseHTTPRequestHandler
from http.cookies import SimpleCookie, CookieError # noqa
from http.client import BadStatusLine, HTTPConnection, IncompleteRead # noqa
from http.client import NotConnected # noqa
from http.server import BaseHTTPRequestHandler # noqa
# Some platforms don't expose HTTPSConnection, so handle it separately
if six.PY3:
@ -222,29 +215,6 @@ except NameError:
# Python 3
xrange = range
import threading
if hasattr(threading.Thread, "daemon"):
# Python 2.6+
def get_daemon(t):
return t.daemon
def set_daemon(t, val):
t.daemon = val
else:
def get_daemon(t):
return t.isDaemon()
def set_daemon(t, val):
t.setDaemon(val)
try:
from email.utils import formatdate
def HTTPDate(timeval=None):
return formatdate(timeval, usegmt=True)
except ImportError:
from rfc822 import formatdate as HTTPDate
try:
# Python 3
from urllib.parse import unquote as parse_unquote
@ -291,15 +261,14 @@ finally:
else:
json_encode = _json_encode
text_or_bytes = six.text_type, six.binary_type
try:
import cPickle as pickle
except ImportError:
# In Python 2, pickle is a Python version.
# In Python 3, pickle is the sped-up C version.
import pickle
import binascii
import pickle # noqa
def random20():
return binascii.hexlify(os.urandom(20)).decode('ascii')
@ -307,7 +276,7 @@ def random20():
try:
from _thread import get_ident as get_thread_ident
except ImportError:
from thread import get_ident as get_thread_ident
from thread import get_ident as get_thread_ident # noqa
try:
# Python 3
@ -325,17 +294,41 @@ else:
Timer = threading._Timer
Event = threading._Event
# Prior to Python 2.6, the Thread class did not have a .daemon property.
# This mix-in adds that property.
class SetDaemonProperty:
def __get_daemon(self):
return self.isDaemon()
def __set_daemon(self, daemon):
self.setDaemon(daemon)
try:
# Python 2.7+
from subprocess import _args_from_interpreter_flags
except ImportError:
def _args_from_interpreter_flags():
"""Tries to reconstruct original interpreter args from sys.flags for Python 2.6
if sys.version_info < (2, 6):
daemon = property(__get_daemon, __set_daemon)
Backported from Python 3.5. Aims to return a list of
command-line arguments reproducing the current
settings in sys.flags and sys.warnoptions.
"""
flag_opt_map = {
'debug': 'd',
# 'inspect': 'i',
# 'interactive': 'i',
'optimize': 'O',
'dont_write_bytecode': 'B',
'no_user_site': 's',
'no_site': 'S',
'ignore_environment': 'E',
'verbose': 'v',
'bytes_warning': 'b',
'quiet': 'q',
'hash_randomization': 'R',
'py3k_warning': '3',
}
args = []
for flag, opt in flag_opt_map.items():
v = getattr(sys.flags, flag)
if v > 0:
if flag == 'hash_randomization':
v = 1 # Handle specification of an exact seed
args.append('-' + opt * v)
for opt in sys.warnoptions:
args.append('-W' + opt)
return args

1544
cherrypy/_cpcompat_subprocess.py

File diff suppressed because it is too large

80
cherrypy/_cpconfig.py

@ -119,7 +119,7 @@ style) context manager.
"""
import cherrypy
from cherrypy._cpcompat import basestring
from cherrypy._cpcompat import text_or_bytes
from cherrypy.lib import reprconf
# Deprecated in CherryPy 3.2--remove in 3.3
@ -132,16 +132,16 @@ def merge(base, other):
If the given config is a filename, it will be appended to
the list of files to monitor for "autoreload" changes.
"""
if isinstance(other, basestring):
if isinstance(other, text_or_bytes):
cherrypy.engine.autoreload.files.add(other)
# Load other into base
for section, value_map in reprconf.as_dict(other).items():
if not isinstance(value_map, dict):
raise ValueError(
"Application config must include section headers, but the "
'Application config must include section headers, but the '
"config you tried to merge doesn't have any sections. "
"Wrap your config in another dict with paths as section "
'Wrap your config in another dict with paths as section '
"headers, for example: {'/': config}.")
base.setdefault(section, {}).update(value_map)
@ -152,19 +152,19 @@ class Config(reprconf.Config):
def update(self, config):
"""Update self from a dict, file or filename."""
if isinstance(config, basestring):
if isinstance(config, text_or_bytes):
# Filename
cherrypy.engine.autoreload.files.add(config)
reprconf.Config.update(self, config)
def _apply(self, config):
"""Update self from a dict."""
if isinstance(config.get("global"), dict):
if isinstance(config.get('global'), dict):
if len(config) > 1:
cherrypy.checker.global_config_contained_paths = True
config = config["global"]
config = config['global']
if 'tools.staticdir.dir' in config:
config['tools.staticdir.section'] = "global"
config['tools.staticdir.section'] = 'global'
reprconf.Config._apply(self, config)
@staticmethod
@ -172,8 +172,8 @@ class Config(reprconf.Config):
"""Decorator for page handlers to set _cp_config."""
if args:
raise TypeError(
"The cherrypy.config decorator does not accept positional "
"arguments; you must use keyword arguments.")
'The cherrypy.config decorator does not accept positional '
'arguments; you must use keyword arguments.')
def tool_decorator(f):
_Vars(f).setdefault('_cp_config', {}).update(kwargs)
@ -197,14 +197,14 @@ class _Vars(object):
# Sphinx begin config.environments
Config.environments = environments = {
"staging": {
'staging': {
'engine.autoreload.on': False,
'checker.on': False,
'tools.log_headers.on': False,
'request.show_tracebacks': False,
'request.show_mismatched_params': False,
},
"production": {
'production': {
'engine.autoreload.on': False,
'checker.on': False,
'tools.log_headers.on': False,
@ -212,7 +212,7 @@ Config.environments = environments = {
'request.show_mismatched_params': False,
'log.screen': False,
},
"embedded": {
'embedded': {
# For use with CherryPy embedded in another deployment stack.
'engine.autoreload.on': False,
'checker.on': False,
@ -223,7 +223,7 @@ Config.environments = environments = {
'engine.SIGHUP': None,
'engine.SIGTERM': None,
},
"test_suite": {
'test_suite': {
'engine.autoreload.on': False,
'checker.on': False,
'tools.log_headers.on': False,
@ -237,11 +237,11 @@ Config.environments = environments = {
def _server_namespace_handler(k, v):
"""Config handler for the "server" namespace."""
atoms = k.split(".", 1)
atoms = k.split('.', 1)
if len(atoms) > 1:
# Special-case config keys of the form 'server.servername.socket_port'
# to configure additional HTTP servers.
if not hasattr(cherrypy, "servers"):
if not hasattr(cherrypy, 'servers'):
cherrypy.servers = {}
servername, k = atoms
@ -260,45 +260,19 @@ def _server_namespace_handler(k, v):
setattr(cherrypy.servers[servername], k, v)
else:
setattr(cherrypy.server, k, v)
Config.namespaces["server"] = _server_namespace_handler
Config.namespaces['server'] = _server_namespace_handler
def _engine_namespace_handler(k, v):
"""Backward compatibility handler for the "engine" namespace."""
"""Config handler for the "engine" namespace."""
engine = cherrypy.engine
deprecated = {
'autoreload_on': 'autoreload.on',
'autoreload_frequency': 'autoreload.frequency',
'autoreload_match': 'autoreload.match',
'reload_files': 'autoreload.files',
'deadlock_poll_freq': 'timeout_monitor.frequency'
}
if k in deprecated:
engine.log(
'WARNING: Use of engine.%s is deprecated and will be removed in a '
'future version. Use engine.%s instead.' % (k, deprecated[k]))
if k == 'autoreload_on':
if v:
engine.autoreload.subscribe()
else:
engine.autoreload.unsubscribe()
elif k == 'autoreload_frequency':
engine.autoreload.frequency = v
elif k == 'autoreload_match':
engine.autoreload.match = v
elif k == 'reload_files':
engine.autoreload.files = set(v)
elif k == 'deadlock_poll_freq':
engine.timeout_monitor.frequency = v
elif k == 'SIGHUP':
engine.listeners['SIGHUP'] = set([v])
if k == 'SIGHUP':
engine.subscribe('SIGHUP', v)
elif k == 'SIGTERM':
engine.listeners['SIGTERM'] = set([v])
elif "." in k:
plugin, attrname = k.split(".", 1)
engine.subscribe('SIGTERM', v)
elif '.' in k:
plugin, attrname = k.split('.', 1)
plugin = getattr(engine, plugin)
if attrname == 'on':
if v and hasattr(getattr(plugin, 'subscribe', None), '__call__'):
@ -313,7 +287,7 @@ def _engine_namespace_handler(k, v):
setattr(plugin, attrname, v)
else:
setattr(engine, k, v)
Config.namespaces["engine"] = _engine_namespace_handler
Config.namespaces['engine'] = _engine_namespace_handler
def _tree_namespace_handler(k, v):
@ -321,9 +295,9 @@ def _tree_namespace_handler(k, v):
if isinstance(v, dict):
for script_name, app in v.items():
cherrypy.tree.graft(app, script_name)
msg = "Mounted: %s on %s" % (app, script_name or "/")
msg = 'Mounted: %s on %s' % (app, script_name or '/')
cherrypy.engine.log(msg)
else:
cherrypy.tree.graft(v, v.script_name)
cherrypy.engine.log("Mounted: %s on %s" % (v, v.script_name or "/"))
Config.namespaces["tree"] = _tree_namespace_handler
cherrypy.engine.log('Mounted: %s on %s' % (v, v.script_name or '/'))
Config.namespaces['tree'] = _tree_namespace_handler

84
cherrypy/_cpdispatch.py

@ -39,7 +39,7 @@ class PageHandler(object):
args = property(
get_args,
set_args,
doc="The ordered args should be accessible from post dispatch hooks"
doc='The ordered args should be accessible from post dispatch hooks'
)
def get_kwargs(self):
@ -52,7 +52,7 @@ class PageHandler(object):
kwargs = property(
get_kwargs,
set_kwargs,
doc="The named kwargs should be accessible from post dispatch hooks"
doc='The named kwargs should be accessible from post dispatch hooks'
)
def __call__(self):
@ -153,7 +153,7 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
# arguments it's definitely a 404.
message = None
if show_mismatched_params:
message = "Missing parameters: %s" % ",".join(missing_args)
message = 'Missing parameters: %s' % ','.join(missing_args)
raise cherrypy.HTTPError(404, message=message)
# the extra positional arguments come from the path - 404 Not Found
@ -175,8 +175,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
message = None
if show_mismatched_params:
message = "Multiple values for parameters: "\
"%s" % ",".join(multiple_args)
message = 'Multiple values for parameters: '\
'%s' % ','.join(multiple_args)
raise cherrypy.HTTPError(error, message=message)
if not varkw and varkw_usage > 0:
@ -186,8 +186,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
if extra_qs_params:
message = None
if show_mismatched_params:
message = "Unexpected query string "\
"parameters: %s" % ", ".join(extra_qs_params)
message = 'Unexpected query string '\
'parameters: %s' % ', '.join(extra_qs_params)
raise cherrypy.HTTPError(404, message=message)
# If there were any extra body parameters, it's a 400 Not Found
@ -195,8 +195,8 @@ def test_callable_spec(callable, callable_args, callable_kwargs):
if extra_body_params:
message = None
if show_mismatched_params:
message = "Unexpected body parameters: "\
"%s" % ", ".join(extra_body_params)
message = 'Unexpected body parameters: '\
'%s' % ', '.join(extra_body_params)
raise cherrypy.HTTPError(400, message=message)
@ -244,14 +244,14 @@ if sys.version_info < (3, 0):
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.")
'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.")
raise ValueError('The translate argument must be a dict.')
class Dispatcher(object):
@ -289,7 +289,7 @@ class Dispatcher(object):
if func:
# Decode any leftover %2F in the virtual_path atoms.
vpath = [x.replace("%2F", "/") for x in vpath]
vpath = [x.replace('%2F', '/') for x in vpath]
request.handler = LateParamPageHandler(func, *vpath)
else:
request.handler = cherrypy.NotFound()
@ -323,10 +323,10 @@ class Dispatcher(object):
fullpath_len = len(fullpath)
segleft = fullpath_len
nodeconf = {}
if hasattr(root, "_cp_config"):
if hasattr(root, '_cp_config'):
nodeconf.update(root._cp_config)
if "/" in app.config:
nodeconf.update(app.config["/"])
if '/' in app.config:
nodeconf.update(app.config['/'])
object_trail = [['root', root, nodeconf, segleft]]
node = root
@ -361,9 +361,9 @@ class Dispatcher(object):
if segleft > pre_len:
# No path segment was removed. Raise an error.
raise cherrypy.CherryPyException(
"A vpath segment was added. Custom dispatchers may only "
+ "remove elements. While trying to process "
+ "{0} in {1}".format(name, fullpath)
'A vpath segment was added. Custom dispatchers may only '
+ 'remove elements. While trying to process '
+ '{0} in {1}'.format(name, fullpath)
)
elif segleft == pre_len:
# Assume that the handler used the current path segment, but
@ -375,7 +375,7 @@ class Dispatcher(object):
if node is not None:
# Get _cp_config attached to this node.
if hasattr(node, "_cp_config"):
if hasattr(node, '_cp_config'):
nodeconf.update(node._cp_config)
# Mix in values from app.config for this path.
@ -414,16 +414,16 @@ class Dispatcher(object):
continue
# Try a "default" method on the current leaf.
if hasattr(candidate, "default"):
if hasattr(candidate, 'default'):
defhandler = candidate.default
if getattr(defhandler, 'exposed', False):
# Insert any extra _cp_config from the default handler.
conf = getattr(defhandler, "_cp_config", {})
conf = getattr(defhandler, '_cp_config', {})
object_trail.insert(
i + 1, ["default", defhandler, conf, segleft])
i + 1, ['default', defhandler, conf, segleft])
request.config = set_conf()
# See https://github.com/cherrypy/cherrypy/issues/613
request.is_index = path.endswith("/")
request.is_index = path.endswith('/')
return defhandler, fullpath[fullpath_len - segleft:-1]
# Uncomment the next line to restrict positional params to
@ -470,23 +470,23 @@ class MethodDispatcher(Dispatcher):
if resource:
# Set Allow header
avail = [m for m in dir(resource) if m.isupper()]
if "GET" in avail and "HEAD" not in avail:
avail.append("HEAD")
if 'GET' in avail and 'HEAD' not in avail:
avail.append('HEAD')
avail.sort()
cherrypy.serving.response.headers['Allow'] = ", ".join(avail)
cherrypy.serving.response.headers['Allow'] = ', '.join(avail)
# Find the subhandler
meth = request.method.upper()
func = getattr(resource, meth, None)
if func is None and meth == "HEAD":
func = getattr(resource, "GET", None)
if func is None and meth == 'HEAD':
func = getattr(resource, 'GET', None)
if func:
# Grab any _cp_config on the subhandler.
if hasattr(func, "_cp_config"):
if hasattr(func, '_cp_config'):
request.config.update(func._cp_config)
# Decode any leftover %2F in the virtual_path atoms.
vpath = [x.replace("%2F", "/") for x in vpath]
vpath = [x.replace('%2F', '/') for x in vpath]
request.handler = LateParamPageHandler(func, *vpath)
else:
request.handler = cherrypy.HTTPError(405)
@ -554,28 +554,28 @@ class RoutesDispatcher(object):
# Get config for the root object/path.
request.config = base = cherrypy.config.copy()
curpath = ""
curpath = ''
def merge(nodeconf):
if 'tools.staticdir.dir' in nodeconf:
nodeconf['tools.staticdir.section'] = curpath or "/"
nodeconf['tools.staticdir.section'] = curpath or '/'
base.update(nodeconf)
app = request.app
root = app.root
if hasattr(root, "_cp_config"):
if hasattr(root, '_cp_config'):
merge(root._cp_config)
if "/" in app.config:
merge(app.config["/"])
if '/' in app.config:
merge(app.config['/'])
# Mix in values from app.config.
atoms = [x for x in path_info.split("/") if x]
atoms = [x for x in path_info.split('/') if x]
if atoms:
last = atoms.pop()
else:
last = None
for atom in atoms:
curpath = "/".join((curpath, atom))
curpath = '/'.join((curpath, atom))
if curpath in app.config:
merge(app.config[curpath])
@ -587,14 +587,14 @@ class RoutesDispatcher(object):
if isinstance(controller, classtype):
controller = controller()
# Get config from the controller.
if hasattr(controller, "_cp_config"):
if hasattr(controller, '_cp_config'):
merge(controller._cp_config)
action = result.get('action')
if action is not None:
handler = getattr(controller, action, None)
# Get config from the handler
if hasattr(handler, "_cp_config"):
if hasattr(handler, '_cp_config'):
merge(handler._cp_config)
else:
handler = controller
@ -602,7 +602,7 @@ class RoutesDispatcher(object):
# Do the last path atom here so it can
# override the controller's _cp_config.
if last:
curpath = "/".join((curpath, last))
curpath = '/'.join((curpath, last))
if curpath in app.config:
merge(app.config[curpath])
@ -666,9 +666,9 @@ def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True,
domain = header('Host', '')
if use_x_forwarded_host:
domain = header("X-Forwarded-Host", domain)
domain = header('X-Forwarded-Host', domain)
prefix = domains.get(domain, "")
prefix = domains.get(domain, '')
if prefix:
path_info = httputil.urljoin(prefix, path_info)

74
cherrypy/_cperror.py

@ -115,13 +115,15 @@ Note that you have to explicitly set
and not simply return an error message as a result.
"""
import contextlib
from cgi import escape as _escape
from sys import exc_info as _exc_info
from traceback import format_exception as _format_exception
from xml.sax import saxutils
import six
from cherrypy._cpcompat import basestring, iteritems, ntob
from cherrypy._cpcompat import text_or_bytes, iteritems, ntob
from cherrypy._cpcompat import tonative, urljoin as _urljoin
from cherrypy.lib import httputil as _httputil
@ -148,14 +150,14 @@ class InternalRedirect(CherryPyException):
URL.
"""
def __init__(self, path, query_string=""):
def __init__(self, path, query_string=''):
import cherrypy
self.request = cherrypy.serving.request
self.query_string = query_string
if "?" in path:
if '?' in path:
# Separate any params included in the path
path, self.query_string = path.split("?", 1)
path, self.query_string = path.split('?', 1)
# Note that urljoin will "do the right thing" whether url is:
# 1. a URL relative to root (e.g. "/dummy")
@ -209,7 +211,7 @@ class HTTPRedirect(CherryPyException):
import cherrypy
request = cherrypy.serving.request
if isinstance(urls, basestring):
if isinstance(urls, text_or_bytes):
urls = [urls]
abs_urls = []
@ -236,7 +238,7 @@ class HTTPRedirect(CherryPyException):
else:
status = int(status)
if status < 300 or status > 399:
raise ValueError("status must be between 300 and 399.")
raise ValueError('status must be between 300 and 399.')
self.status = status
CherryPyException.__init__(self, abs_urls, status)
@ -253,7 +255,7 @@ class HTTPRedirect(CherryPyException):
response.status = status = self.status
if status in (300, 301, 302, 303, 307):
response.headers['Content-Type'] = "text/html;charset=utf-8"
response.headers['Content-Type'] = 'text/html;charset=utf-8'
# "The ... URI SHOULD be given by the Location field
# in the response."
response.headers['Location'] = self.urls[0]
@ -262,16 +264,15 @@ class HTTPRedirect(CherryPyException):
# SHOULD contain a short hypertext note with a hyperlink to the
# new URI(s)."
msg = {
300: "This resource can be found at ",
301: "This resource has permanently moved to ",
302: "This resource resides temporarily at ",
303: "This resource can be found at ",
307: "This resource has moved temporarily to ",
300: 'This resource can be found at ',
301: 'This resource has permanently moved to ',
302: 'This resource resides temporarily at ',
303: 'This resource can be found at ',
307: 'This resource has moved temporarily to ',
}[status]
msg += '<a href=%s>%s</a>.'
from xml.sax import saxutils
msgs = [msg % (saxutils.quoteattr(u), u) for u in self.urls]
response.body = ntob("<br />\n".join(msgs), 'utf-8')
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)
@ -301,7 +302,7 @@ class HTTPRedirect(CherryPyException):
# Previous code may have set C-L, so we have to reset it.
response.headers.pop('Content-Length', None)
else:
raise ValueError("The %s status code is unknown." % status)
raise ValueError('The %s status code is unknown.' % status)
def __call__(self):
"""Use this exception as a request.handler (raise self)."""
@ -317,9 +318,9 @@ def clean_headers(status):
# Remove headers which applied to the original content,
# but do not apply to the error page.
respheaders = response.headers
for key in ["Accept-Ranges", "Age", "ETag", "Location", "Retry-After",
"Vary", "Content-Encoding", "Content-Length", "Expires",
"Content-Location", "Content-MD5", "Last-Modified"]:
for key in ['Accept-Ranges', 'Age', 'ETag', 'Location', 'Retry-After',
'Vary', 'Content-Encoding', 'Content-Length', 'Expires',
'Content-Location', 'Content-MD5', 'Last-Modified']:
if key in respheaders:
del respheaders[key]
@ -330,8 +331,8 @@ def clean_headers(status):
# specifies the current length of the selected resource.
# A response with status code 206 (Partial Content) MUST NOT
# include a Content-Range field with a byte-range- resp-spec of "*".
if "Content-Range" in respheaders:
del respheaders["Content-Range"]
if 'Content-Range' in respheaders:
del respheaders['Content-Range']
class HTTPError(CherryPyException):
@ -371,7 +372,7 @@ class HTTPError(CherryPyException):
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.")
raise ValueError('status must be between 400 and 599.')
# See http://www.python.org/dev/peps/pep-0352/
# self.message = message
@ -413,6 +414,15 @@ class HTTPError(CherryPyException):
"""Use this exception as a request.handler (raise self)."""
raise self
@classmethod
@contextlib.contextmanager
def handle(cls, exception, status=500, message=''):
"""Translate exception into an HTTPError."""
try:
yield
except exception as exc:
raise cls(status, message or str(exc))
class NotFound(HTTPError):
@ -480,7 +490,7 @@ def get_error_page(status, **kwargs):
# We can't use setdefault here, because some
# callers send None for kwarg values.
if kwargs.get('status') is None:
kwargs['status'] = "%s %s" % (code, reason)
kwargs['status'] = '%s %s' % (code, reason)
if kwargs.get('message') is None:
kwargs['message'] = message
if kwargs.get('traceback') is None:
@ -490,7 +500,7 @@ def get_error_page(status, **kwargs):
for k, v in iteritems(kwargs):
if v is None:
kwargs[k] = ""
kwargs[k] = ''
else:
kwargs[k] = _escape(kwargs[k])
@ -528,12 +538,12 @@ def get_error_page(status, **kwargs):
e = _format_exception(*_exc_info())[-1]
m = kwargs['message']
if m:
m += "<br />"
m += "In addition, the custom error page failed:\n<br />%s" % e
m += '<br />'
m += 'In addition, the custom error page failed:\n<br />%s' % e
kwargs['message'] = m
response = cherrypy.serving.response
response.headers['Content-Type'] = "text/html;charset=utf-8"
response.headers['Content-Type'] = 'text/html;charset=utf-8'
result = template % kwargs
return result.encode('utf-8')
@ -565,7 +575,7 @@ 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 + (ntob(" ") * (s - l))
content = content + (ntob(' ') * (s - l))
response.body = content
response.headers['Content-Length'] = str(len(content))
@ -576,9 +586,9 @@ def format_exc(exc=None):
if exc is None:
exc = _exc_info()
if exc == (None, None, None):
return ""
return ''
import traceback
return "".join(traceback.format_exception(*exc))
return ''.join(traceback.format_exception(*exc))
finally:
del exc
@ -600,13 +610,13 @@ 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 = ntob("Unrecoverable error in the server.")
body = ntob('Unrecoverable error in the server.')
if extrabody is not None:
if not isinstance(extrabody, bytes):
extrabody = extrabody.encode('utf-8')
body += ntob("\n") + extrabody
body += ntob('\n') + extrabody
return (ntob("500 Internal Server Error"),
return (ntob('500 Internal Server Error'),
[(ntob('Content-Type'), ntob('text/plain')),
(ntob('Content-Length'), ntob(str(len(body)), 'ISO-8859-1'))],
[body])

54
cherrypy/_cplogging.py

@ -109,9 +109,6 @@ the "log.error_file" config entry, for example).
import datetime
import logging
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
logging.Logger.manager.emittedNoHandlerWarning = 1
logfmt = logging.Formatter("%(message)s")
import os
import sys
@ -122,6 +119,11 @@ from cherrypy import _cperror
from cherrypy._cpcompat import ntob
# Silence the no-handlers "warning" (stderr write!) in stdlib logging
logging.Logger.manager.emittedNoHandlerWarning = 1
logfmt = logging.Formatter('%(message)s')
class NullHandler(logging.Handler):
"""A no-op logging handler to silence the logging.lastResort handler."""
@ -170,17 +172,17 @@ class LogManager(object):
cherrypy.access.<appid>
"""
def __init__(self, appid=None, logger_root="cherrypy"):
def __init__(self, appid=None, logger_root='cherrypy'):
self.logger_root = logger_root
self.appid = appid
if appid is None:
self.error_log = logging.getLogger("%s.error" % logger_root)
self.access_log = logging.getLogger("%s.access" % logger_root)
self.error_log = logging.getLogger('%s.error' % logger_root)
self.access_log = logging.getLogger('%s.access' % logger_root)
else:
self.error_log = logging.getLogger(
"%s.error.%s" % (logger_root, appid))
'%s.error.%s' % (logger_root, appid))
self.access_log = logging.getLogger(
"%s.access.%s" % (logger_root, appid))
'%s.access.%s' % (logger_root, appid))
self.error_log.setLevel(logging.INFO)
self.access_log.setLevel(logging.INFO)
@ -244,19 +246,19 @@ class LogManager(object):
outheaders = response.headers
inheaders = request.headers
if response.output_status is None:
status = "-"
status = '-'
else:
status = response.output_status.split(ntob(" "), 1)[0]
status = response.output_status.split(ntob(' '), 1)[0]
if six.PY3:
status = status.decode('ISO-8859-1')
atoms = {'h': remote.name or remote.ip,
'l': '-',
'u': getattr(request, "login", None) or "-",
'u': getattr(request, 'login', None) or '-',
't': self.time(),
'r': request.request_line,
's': status,
'b': dict.get(outheaders, 'Content-Length', '') or "-",
'b': dict.get(outheaders, 'Content-Length', '') or '-',
'f': dict.get(inheaders, 'Referer', ''),
'a': dict.get(inheaders, 'User-Agent', ''),
'o': dict.get(inheaders, 'Host', '-'),
@ -312,26 +314,26 @@ class LogManager(object):
def _get_builtin_handler(self, log, key):
for h in log.handlers:
if getattr(h, "_cpbuiltin", None) == key:
if getattr(h, '_cpbuiltin', None) == key:
return h
# ------------------------- Screen handlers ------------------------- #
def _set_screen_handler(self, log, enable, stream=None):
h = self._get_builtin_handler(log, "screen")
h = self._get_builtin_handler(log, 'screen')
if enable:
if not h:
if stream is None:
stream = sys.stderr
h = logging.StreamHandler(stream)
h.setFormatter(logfmt)
h._cpbuiltin = "screen"
h._cpbuiltin = 'screen'
log.addHandler(h)
elif h:
log.handlers.remove(h)
def _get_screen(self):
h = self._get_builtin_handler
has_h = h(self.error_log, "screen") or h(self.access_log, "screen")
has_h = h(self.error_log, 'screen') or h(self.access_log, 'screen')
return bool(has_h)
def _set_screen(self, newvalue):
@ -349,11 +351,11 @@ class LogManager(object):
def _add_builtin_file_handler(self, log, fname):
h = logging.FileHandler(fname)
h.setFormatter(logfmt)
h._cpbuiltin = "file"
h._cpbuiltin = 'file'
log.addHandler(h)
def _set_file_handler(self, log, filename):
h = self._get_builtin_handler(log, "file")
h = self._get_builtin_handler(log, 'file')
if filename:
if h:
if h.baseFilename != os.path.abspath(filename):
@ -368,7 +370,7 @@ class LogManager(object):
log.handlers.remove(h)
def _get_error_file(self):
h = self._get_builtin_handler(self.error_log, "file")
h = self._get_builtin_handler(self.error_log, 'file')
if h:
return h.baseFilename
return ''
@ -383,7 +385,7 @@ class LogManager(object):
""")
def _get_access_file(self):
h = self._get_builtin_handler(self.access_log, "file")
h = self._get_builtin_handler(self.access_log, 'file')
if h:
return h.baseFilename
return ''
@ -400,18 +402,18 @@ class LogManager(object):
# ------------------------- WSGI handlers ------------------------- #
def _set_wsgi_handler(self, log, enable):
h = self._get_builtin_handler(log, "wsgi")
h = self._get_builtin_handler(log, 'wsgi')
if enable:
if not h:
h = WSGIErrorHandler()
h.setFormatter(logfmt)
h._cpbuiltin = "wsgi"
h._cpbuiltin = 'wsgi'
log.addHandler(h)
elif h:
log.handlers.remove(h)
def _get_wsgi(self):
return bool(self._get_builtin_handler(self.error_log, "wsgi"))
return bool(self._get_builtin_handler(self.error_log, 'wsgi'))
def _set_wsgi(self, newvalue):
self._set_wsgi_handler(self.error_log, newvalue)
@ -447,16 +449,16 @@ class WSGIErrorHandler(logging.Handler):
else:
try:
msg = self.format(record)
fs = "%s\n"
fs = '%s\n'
import types
# if no unicode support...
if not hasattr(types, "UnicodeType"):
if not hasattr(types, 'UnicodeType'):
stream.write(fs % msg)
else:
try:
stream.write(fs % msg)
except UnicodeError:
stream.write(fs % msg.encode("UTF-8"))
stream.write(fs % msg.encode('UTF-8'))
self.flush()
except:
self.handleError(record)

68
cherrypy/_cpmodpy.py

@ -55,9 +55,11 @@ resides in the global site-package this won't be needed.
Then restart apache2 and access http://127.0.0.1:8080
"""
import io
import logging
import os
import re
import sys
import io
import cherrypy
from cherrypy._cpcompat import copyitems, ntob
@ -86,14 +88,14 @@ def setup(req):
func()
cherrypy.config.update({'log.screen': False,
"tools.ignore_headers.on": True,
"tools.ignore_headers.headers": ['Range'],
'tools.ignore_headers.on': True,
'tools.ignore_headers.headers': ['Range'],
})
engine = cherrypy.engine
if hasattr(engine, "signal_handler"):
if hasattr(engine, 'signal_handler'):
engine.signal_handler.unsubscribe()
if hasattr(engine, "console_control_handler"):
if hasattr(engine, 'console_control_handler'):
engine.console_control_handler.unsubscribe()
engine.autoreload.unsubscribe()
cherrypy.server.unsubscribe()
@ -147,10 +149,10 @@ def handler(req):
# Obtain a Request object from CherryPy
local = req.connection.local_addr
local = httputil.Host(
local[0], local[1], req.connection.local_host or "")
local[0], local[1], req.connection.local_host or '')
remote = req.connection.remote_addr
remote = httputil.Host(
remote[0], remote[1], req.connection.remote_host or "")
remote[0], remote[1], req.connection.remote_host or '')
scheme = req.parsed_uri[0] or 'http'
req.get_basic_auth_pw()
@ -163,7 +165,7 @@ def handler(req):
except AttributeError:
bad_value = ("You must provide a PythonOption '%s', "
"either 'on' or 'off', when running a version "
"of mod_python < 3.1")
'of mod_python < 3.1')
threaded = options.get('multithread', '').lower()
if threaded == 'on':
@ -171,7 +173,7 @@ def handler(req):
elif threaded == 'off':
threaded = False
else:
raise ValueError(bad_value % "multithread")
raise ValueError(bad_value % 'multithread')
forked = options.get('multiprocess', '').lower()
if forked == 'on':
@ -179,16 +181,16 @@ def handler(req):
elif forked == 'off':
forked = False
else:
raise ValueError(bad_value % "multiprocess")
raise ValueError(bad_value % 'multiprocess')
sn = cherrypy.tree.script_name(req.uri or "/")
sn = cherrypy.tree.script_name(req.uri or '/')
if sn is None:
send_response(req, '404 Not Found', [], '')
else:
app = cherrypy.tree.apps[sn]
method = req.method
path = req.uri
qs = req.args or ""
qs = req.args or ''
reqproto = req.protocol
headers = copyitems(req.headers_in)
rfile = _ReadOnlyRequest(req)
@ -198,7 +200,7 @@ def handler(req):
redirections = []
while True:
request, response = app.get_serving(local, remote, scheme,
"HTTP/1.1")
'HTTP/1.1')
request.login = req.user
request.multithread = bool(threaded)
request.multiprocess = bool(forked)
@ -217,17 +219,17 @@ def handler(req):
if not recursive:
if ir.path in redirections:
raise RuntimeError(
"InternalRedirector visited the same URL "
"twice: %r" % ir.path)
'InternalRedirector visited the same URL '
'twice: %r' % ir.path)
else:
# Add the *previous* path_info + qs to
# redirections.
if qs:
qs = "?" + qs
qs = '?' + qs
redirections.append(sn + path + qs)
# Munge environment and try again.
method = "GET"
method = 'GET'
path = ir.path
qs = ir.query_string
rfile = io.BytesIO()
@ -250,7 +252,7 @@ def send_response(req, status, headers, body, stream=False):
req.status = int(status[:3])
# Set response headers
req.content_type = "text/plain"
req.content_type = 'text/plain'
for header, value in headers:
if header.lower() == 'content-type':
req.content_type = value
@ -262,7 +264,7 @@ def send_response(req, status, headers, body, stream=False):
req.flush()
# Set response body
if isinstance(body, basestring):
if isinstance(body, text_or_bytes):
req.write(body)
else:
for seg in body:
@ -270,8 +272,6 @@ def send_response(req, status, headers, body, stream=False):
# --------------- Startup tools for CherryPy + mod_python --------------- #
import os
import re
try:
import subprocess
@ -286,13 +286,13 @@ except ImportError:
return pipeout
def read_process(cmd, args=""):
fullcmd = "%s %s" % (cmd, args)
def read_process(cmd, args=''):
fullcmd = '%s %s' % (cmd, args)
pipeout = popen(fullcmd)
try:
firstline = pipeout.readline()
cmd_not_found = re.search(
ntob("(not recognized|No such file|not found)"),
ntob('(not recognized|No such file|not found)'),
firstline,
re.IGNORECASE
)
@ -321,8 +321,8 @@ LoadModule python_module modules/mod_python.so
</Location>
"""
def __init__(self, loc="/", port=80, opts=None, apache_path="apache",
handler="cherrypy._cpmodpy::handler"):
def __init__(self, loc='/', port=80, opts=None, apache_path='apache',
handler='cherrypy._cpmodpy::handler'):
self.loc = loc
self.port = port
self.opts = opts
@ -330,25 +330,25 @@ LoadModule python_module modules/mod_python.so
self.handler = handler
def start(self):
opts = "".join([" PythonOption %s %s\n" % (k, v)
opts = ''.join([' PythonOption %s %s\n' % (k, v)
for k, v in self.opts])
conf_data = self.template % {"port": self.port,
"loc": self.loc,
"opts": opts,
"handler": self.handler,
conf_data = self.template % {'port': self.port,
'loc': self.loc,
'opts': opts,
'handler': self.handler,
}
mpconf = os.path.join(os.path.dirname(__file__), "cpmodpy.conf")
mpconf = os.path.join(os.path.dirname(__file__), 'cpmodpy.conf')
f = open(mpconf, 'wb')
try:
f.write(conf_data)
finally:
f.close()
response = read_process(self.apache_path, "-k start -f %s" % mpconf)
response = read_process(self.apache_path, '-k start -f %s' % mpconf)
self.ready = True
return response
def stop(self):
os.popen("apache -k stop")
os.popen('apache -k stop')
self.ready = False

20
cherrypy/_cpnative_server.py

@ -19,19 +19,19 @@ class NativeGateway(wsgiserver.Gateway):
try:
# Obtain a Request object from CherryPy
local = req.server.bind_addr
local = httputil.Host(local[0], local[1], "")
local = httputil.Host(local[0], local[1], '')
remote = req.conn.remote_addr, req.conn.remote_port
remote = httputil.Host(remote[0], remote[1], "")
remote = httputil.Host(remote[0], remote[1], '')
scheme = req.scheme
sn = cherrypy.tree.script_name(req.uri or "/")
sn = cherrypy.tree.script_name(req.uri or '/')
if sn is None:
self.send_response('404 Not Found', [], [''])
else:
app = cherrypy.tree.apps[sn]
method = req.method
path = req.path
qs = req.qs or ""
qs = req.qs or ''
headers = req.inheaders.items()
rfile = req.rfile
prev = None
@ -40,7 +40,7 @@ class NativeGateway(wsgiserver.Gateway):
redirections = []
while True:
request, response = app.get_serving(
local, remote, scheme, "HTTP/1.1")
local, remote, scheme, 'HTTP/1.1')
request.multithread = True
request.multiprocess = False
request.app = app
@ -60,17 +60,17 @@ class NativeGateway(wsgiserver.Gateway):
if not self.recursive:
if ir.path in redirections:
raise RuntimeError(
"InternalRedirector visited the same "
"URL twice: %r" % ir.path)
'InternalRedirector visited the same '
'URL twice: %r' % ir.path)
else:
# Add the *previous* path_info + qs to
# redirections.
if qs:
qs = "?" + qs
qs = '?' + qs
redirections.append(sn + path + qs)
# Munge environment and try again.
method = "GET"
method = 'GET'
path = ir.path
qs = ir.query_string
rfile = io.BytesIO()
@ -91,7 +91,7 @@ class NativeGateway(wsgiserver.Gateway):
req = self.req
# Set response status
req.status = str(status or "500 Server Error")
req.status = str(status or '500 Server Error')
# Set response headers
for header, value in headers:

80
cherrypy/_cpreqbody.py

@ -132,7 +132,7 @@ except ImportError:
return ntob('').join(atoms)
import cherrypy
from cherrypy._cpcompat import basestring, ntob, ntou
from cherrypy._cpcompat import text_or_bytes, ntob, ntou
from cherrypy.lib import httputil
@ -169,8 +169,8 @@ def process_urlencoded(entity):
break
else:
raise cherrypy.HTTPError(
400, "The request entity could not be decoded. The following "
"charsets were attempted: %s" % repr(entity.attempt_charsets))
400, 'The request entity could not be decoded. The following '
'charsets were attempted: %s' % repr(entity.attempt_charsets))
# Now that all values have been successfully parsed and decoded,
# apply them to the entity.params dict.
@ -185,7 +185,7 @@ def process_urlencoded(entity):
def process_multipart(entity):
"""Read all multipart parts into entity.parts."""
ib = ""
ib = ''
if 'boundary' in entity.content_type.params:
# http://tools.ietf.org/html/rfc2046#section-5.1.1
# "The grammar for parameters on the Content-type field is such that it
@ -193,7 +193,7 @@ def process_multipart(entity):
# on the Content-type line"
ib = entity.content_type.params['boundary'].strip('"')
if not re.match("^[ -~]{0,200}[!-~]$", ib):
if not re.match('^[ -~]{0,200}[!-~]$', ib):
raise ValueError('Invalid boundary in multipart form: %r' % (ib,))
ib = ('--' + ib).encode('ascii')
@ -428,7 +428,7 @@ class Entity(object):
# Copy the class 'attempt_charsets', prepending any Content-Type
# charset
dec = self.content_type.params.get("charset", None)
dec = self.content_type.params.get('charset', None)
if dec:
self.attempt_charsets = [dec] + [c for c in self.attempt_charsets
if c != dec]
@ -469,8 +469,8 @@ class Entity(object):
# The 'type' attribute is deprecated in 3.2; remove it in 3.3.
type = property(
lambda self: self.content_type,
doc="A deprecated alias for "
":attr:`content_type<cherrypy._cpreqbody.Entity.content_type>`."
doc='A deprecated alias for '
':attr:`content_type<cherrypy._cpreqbody.Entity.content_type>`.'
)
def read(self, size=None, fp_out=None):
@ -536,8 +536,8 @@ class Entity(object):
else:
raise cherrypy.HTTPError(
400,
"The request entity could not be decoded. The following "
"charsets were attempted: %s" % repr(self.attempt_charsets)
'The request entity could not be decoded. The following '
'charsets were attempted: %s' % repr(self.attempt_charsets)
)
def process(self):
@ -613,40 +613,40 @@ class Part(Entity):
self.file = None
self.value = None
@classmethod
def from_fp(cls, fp, boundary):
headers = cls.read_headers(fp)
return cls(fp, headers, boundary)
from_fp = classmethod(from_fp)
@classmethod
def read_headers(cls, fp):
headers = httputil.HeaderMap()
while True:
line = fp.readline()
if not line:
# No more data--illegal end of headers
raise EOFError("Illegal end of headers.")
raise EOFError('Illegal end of headers.')
if line == ntob('\r\n') or line == ntob('\n'):
if line == ntob('\r\n'):
# Normal end of headers
break
if not line.endswith(ntob('\n')):
raise ValueError("MIME requires CRLF terminators: %r" % line)
if not line.endswith(ntob('\r\n')):
raise ValueError('MIME requires CRLF terminators: %r' % line)
if line[0] in ntob(' \t'):
# It's a continuation line.
v = line.strip().decode('ISO-8859-1')
else:
k, v = line.split(ntob(":"), 1)
k, v = line.split(ntob(':'), 1)
k = k.strip().decode('ISO-8859-1')
v = v.strip().decode('ISO-8859-1')
existing = headers.get(k)
if existing:
v = ", ".join((existing, v))
v = ', '.join((existing, v))
headers[k] = v
return headers
read_headers = classmethod(read_headers)
def read_lines_to_boundary(self, fp_out=None):
"""Read bytes from self.fp and return or write them to a file.
@ -658,16 +658,16 @@ class Part(Entity):
object that supports the 'write' method; all bytes read will be
written to the fp, and that fp is returned.
"""
endmarker = self.boundary + ntob("--")
delim = ntob("")
endmarker = self.boundary + ntob('--')
delim = ntob('')
prev_lf = True
lines = []
seen = 0
while True:
line = self.fp.readline(1 << 16)
if not line:
raise EOFError("Illegal end of multipart body.")
if line.startswith(ntob("--")) and prev_lf:
raise EOFError('Illegal end of multipart body.')
if line.startswith(ntob('--')) and prev_lf:
strippedline = line.strip()
if strippedline == self.boundary:
break
@ -677,16 +677,16 @@ class Part(Entity):
line = delim + line
if line.endswith(ntob("\r\n")):
delim = ntob("\r\n")
if line.endswith(ntob('\r\n')):
delim = ntob('\r\n')
line = line[:-2]
prev_lf = True
elif line.endswith(ntob("\n")):
delim = ntob("\n")
elif line.endswith(ntob('\n')):
delim = ntob('\n')
line = line[:-1]
prev_lf = True
else:
delim = ntob("")
delim = ntob('')
prev_lf = False
if fp_out is None:
@ -715,7 +715,7 @@ class Part(Entity):
self.file = self.read_into_file()
else:
result = self.read_lines_to_boundary()
if isinstance(result, basestring):
if isinstance(result, text_or_bytes):
self.value = result
else:
self.file = result
@ -732,19 +732,7 @@ class Part(Entity):
Entity.part_class = Part
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()
inf = float('inf')
comma_separated_headers = [
'Accept', 'Accept-Charset', 'Accept-Encoding',
@ -839,7 +827,7 @@ class SizedReader:
if e.__class__.__name__ == 'MaxSizeExceeded':
# Post data is too big
raise cherrypy.HTTPError(
413, "Maximum request length: %r" % e.args[1])
413, 'Maximum request length: %r' % e.args[1])
else:
raise
if not data:
@ -915,23 +903,23 @@ class SizedReader:
v = line.strip()
else:
try:
k, v = line.split(ntob(":"), 1)
k, v = line.split(ntob(':'), 1)
except ValueError:
raise ValueError("Illegal header line.")
raise ValueError('Illegal header line.')
k = k.strip().title()
v = v.strip()
if k in comma_separated_headers:
existing = self.trailers.get(envname)
if existing:
v = ntob(", ").join((existing, v))
v = ntob(', ').join((existing, v))
self.trailers[k] = v
except Exception:
e = sys.exc_info()[1]
if e.__class__.__name__ == 'MaxSizeExceeded':
# Post data is too big
raise cherrypy.HTTPError(
413, "Maximum request length: %r" % e.args[1])
413, 'Maximum request length: %r' % e.args[1])
else:
raise

113
cherrypy/_cprequest.py

@ -5,7 +5,7 @@ import warnings
import six
import cherrypy
from cherrypy._cpcompat import basestring, copykeys, ntob
from cherrypy._cpcompat import text_or_bytes, copykeys, ntob
from cherrypy._cpcompat import SimpleCookie, CookieError
from cherrypy import _cpreqbody, _cpconfig
from cherrypy._cperror import format_exc, bare_error
@ -41,11 +41,11 @@ class Hook(object):
self.callback = callback
if failsafe is None:
failsafe = getattr(callback, "failsafe", False)
failsafe = getattr(callback, 'failsafe', False)
self.failsafe = failsafe
if priority is None:
priority = getattr(callback, "priority", 50)
priority = getattr(callback, 'priority', 50)
self.priority = priority
self.kwargs = kwargs
@ -64,10 +64,10 @@ class Hook(object):
def __repr__(self):
cls = self.__class__
return ("%s.%s(callback=%r, failsafe=%r, priority=%r, %s)"
return ('%s.%s(callback=%r, failsafe=%r, priority=%r, %s)'
% (cls.__module__, cls.__name__, self.callback,
self.failsafe, self.priority,
", ".join(['%s=%r' % (k, v)
', '.join(['%s=%r' % (k, v)
for k, v in self.kwargs.items()])))
@ -124,7 +124,7 @@ class HookMap(dict):
def __repr__(self):
cls = self.__class__
return "%s.%s(points=%r)" % (
return '%s.%s(points=%r)' % (
cls.__module__,
cls.__name__,
copykeys(self)
@ -138,8 +138,8 @@ def hooks_namespace(k, v):
# Use split again to allow multiple hooks for a single
# hookpoint per path (e.g. "hooks.before_handler.1").
# Little-known fact you only get from reading source ;)
hookpoint = k.split(".", 1)[0]
if isinstance(v, basestring):
hookpoint = k.split('.', 1)[0]
if isinstance(v, text_or_bytes):
v = cherrypy.lib.attributes(v)
if not isinstance(v, Hook):
v = Hook(v)
@ -199,23 +199,23 @@ class Request(object):
unless we are processing an InternalRedirect."""
# Conversation/connection attributes
local = httputil.Host("127.0.0.1", 80)
"An httputil.Host(ip, port, hostname) object for the server socket."
local = httputil.Host('127.0.0.1', 80)
'An httputil.Host(ip, port, hostname) object for the server socket.'
remote = httputil.Host("127.0.0.1", 1111)
"An httputil.Host(ip, port, hostname) object for the client socket."
remote = httputil.Host('127.0.0.1', 1111)
'An httputil.Host(ip, port, hostname) object for the client socket.'
scheme = "http"
scheme = 'http'
"""
The protocol used between client and server. In most cases,
this will be either 'http' or 'https'."""
server_protocol = "HTTP/1.1"
server_protocol = 'HTTP/1.1'
"""
The HTTP version for which the HTTP server is at least
conditionally compliant."""
base = ""
base = ''
"""The (scheme://host) portion of the requested URL.
In some cases (e.g. when proxying via mod_rewrite), this may contain
path segments which cherrypy.url uses when constructing url's, but
@ -223,13 +223,13 @@ class Request(object):
MUST NOT end in a slash."""
# Request-Line attributes
request_line = ""
request_line = ''
"""
The complete Request-Line received from the client. This is a
single string consisting of the request method, URI, and protocol
version (joined by spaces). Any final CRLF is removed."""
method = "GET"
method = 'GET'
"""
Indicates the HTTP method to be performed on the resource identified
by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
@ -237,7 +237,7 @@ class Request(object):
servers and gateways may restrict the set of allowable methods.
CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
query_string = ""
query_string = ''
"""
The query component of the Request-URI, a string of information to be
interpreted by the resource. The query portion of a URI follows the
@ -312,7 +312,7 @@ class Request(object):
If True, the rfile (if any) is automatically read and parsed,
and the result placed into request.params or request.body."""
methods_with_bodies = ("POST", "PUT")
methods_with_bodies = ('POST', 'PUT')
"""
A sequence of HTTP methods for which CherryPy will automatically
attempt to read a body from the rfile. If you are going to change
@ -341,7 +341,7 @@ class Request(object):
to a hierarchical arrangement of objects, starting at request.app.root.
See help(cherrypy.dispatch) for more information."""
script_name = ""
script_name = ''
"""
The 'mount point' of the application which is handling this request.
@ -349,7 +349,7 @@ class Request(object):
the root of the URI, it MUST be an empty string (not "/").
"""
path_info = "/"
path_info = '/'
"""
The 'relative path' portion of the Request-URI. This is relative
to the script_name ('mount point') of the application which is
@ -468,15 +468,15 @@ class Request(object):
This is useful when debugging a live server with hung requests."""
namespaces = _cpconfig.NamespaceSet(
**{"hooks": hooks_namespace,
"request": request_namespace,
"response": response_namespace,
"error_page": error_page_namespace,
"tools": cherrypy.tools,
**{'hooks': hooks_namespace,
'request': request_namespace,
'response': response_namespace,
'error_page': error_page_namespace,
'tools': cherrypy.tools,
})
def __init__(self, local_host, remote_host, scheme="http",
server_protocol="HTTP/1.1"):
def __init__(self, local_host, remote_host, scheme='http',
server_protocol='HTTP/1.1'):
"""Populate a new Request object.
local_host should be an httputil.Host object with the server info.
@ -544,7 +544,7 @@ class Request(object):
self.error_response = cherrypy.HTTPError(500).set_response
self.method = method
path = path or "/"
path = path or '/'
self.query_string = query_string or ''
self.params = {}
@ -600,11 +600,11 @@ class Request(object):
if self.show_tracebacks:
body = format_exc()
else:
body = ""
body = ''
r = bare_error(body)
response.output_status, response.header_list, response.body = r
if self.method == "HEAD":
if self.method == 'HEAD':
# HEAD requests MUST NOT return a message-body in the response.
response.body = []
@ -696,8 +696,8 @@ class Request(object):
self.query_string, encoding=self.query_string_encoding)
except UnicodeDecodeError:
raise cherrypy.HTTPError(
404, "The given query string could not be processed. Query "
"strings for this resource must be encoded with %r." %
404, 'The given query string could not be processed. Query '
'strings for this resource must be encoded with %r.' %
self.query_string_encoding)
# Python 2 only: keyword arguments must be byte strings (type 'str').
@ -722,7 +722,7 @@ class Request(object):
# (AFAIK, only Konqueror does that), only the last one will
# remain in headers (but they will be correctly stored in
# request.cookie).
if "=?" in value:
if '=?' in value:
dict.__setitem__(headers, name, httputil.decode_TEXT(value))
else:
dict.__setitem__(headers, name, value)
@ -733,7 +733,7 @@ class Request(object):
try:
self.cookie.load(value)
except CookieError:
msg = "Illegal cookie name %s" % value.split('=')[0]
msg = 'Illegal cookie name %s' % value.split('=')[0]
raise cherrypy.HTTPError(400, msg)
if not dict.__contains__(headers, 'Host'):
@ -746,7 +746,7 @@ class Request(object):
host = dict.get(headers, 'Host')
if not host:
host = self.local.name or self.local.ip
self.base = "%s://%s" % (self.scheme, host)
self.base = '%s://%s' % (self.scheme, host)
def get_resource(self, path):
"""Call a dispatcher (which sets self.handler and .config). (Core)"""
@ -754,7 +754,7 @@ class Request(object):
# dispatchers can only be specified in app.config, not in _cp_config
# (since custom dispatchers may not even have an app.root).
dispatch = self.app.find_config(
path, "request.dispatch", self.dispatch)
path, 'request.dispatch', self.dispatch)
# dispatch() should set self.handler and self.config
dispatch(path)
@ -762,10 +762,10 @@ class Request(object):
def handle_error(self):
"""Handle the last unanticipated exception. (Core)"""
try:
self.hooks.run("before_error_response")
self.hooks.run('before_error_response')
if self.error_response:
self.error_response()
self.hooks.run("after_error_response")
self.hooks.run('after_error_response')
cherrypy.serving.response.finalize()
except cherrypy.HTTPRedirect:
inst = sys.exc_info()[1]
@ -776,8 +776,8 @@ class Request(object):
def _get_body_params(self):
warnings.warn(
"body_params is deprecated in CherryPy 3.2, will be removed in "
"CherryPy 3.3.",
'body_params is deprecated in CherryPy 3.2, will be removed in '
'CherryPy 3.3.',
DeprecationWarning
)
return self.body.params
@ -800,8 +800,8 @@ class ResponseBody(object):
"""The body of the HTTP response (the response entity)."""
if six.PY3:
unicode_err = ("Page handlers MUST return bytes. Use tools.encode "
"if you wish to return unicode.")
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:
@ -815,7 +815,7 @@ class ResponseBody(object):
if six.PY3 and isinstance(value, str):
raise ValueError(self.unicode_err)
if isinstance(value, basestring):
if isinstance(value, text_or_bytes):
# strings get wrapped in a list because iterating over a single
# item list is much faster than iterating over every character
# in a long string.
@ -842,7 +842,7 @@ class Response(object):
"""An HTTP Response, including status, headers, and body."""
status = ""
status = ''
"""The HTTP Status-Code and Reason-Phrase."""
header_list = []
@ -893,15 +893,15 @@ class Response(object):
# Since we know all our keys are titled strings, we can
# bypass HeaderMap.update and get a big speed boost.
dict.update(self.headers, {
"Content-Type": 'text/html',
"Server": "CherryPy/" + cherrypy.__version__,
"Date": httputil.HTTPDate(self.time),
'Content-Type': 'text/html',
'Server': 'CherryPy/' + cherrypy.__version__,
'Date': httputil.HTTPDate(self.time),
})
self.cookie = SimpleCookie()
def collapse_body(self):
"""Collapse self.body to a single string; replace it and return it."""
if isinstance(self.body, basestring):
if isinstance(self.body, text_or_bytes):
return self.body
newbody = []
@ -924,9 +924,9 @@ class Response(object):
headers = self.headers
self.status = "%s %s" % (code, reason)
self.status = '%s %s' % (code, reason)
self.output_status = ntob(str(code), 'ascii') + \
ntob(" ") + headers.encode(reason)
ntob(' ') + headers.encode(reason)
if self.stream:
# The upshot: wsgiserver will chunk the response if
@ -939,7 +939,7 @@ class Response(object):
# and 304 (not modified) responses MUST NOT
# include a message-body."
dict.pop(headers, 'Content-Length', None)
self.body = ntob("")
self.body = ntob('')
else:
# Responses which are not streamed should have a Content-Length,
# but allow user code to set Content-Length if desired.
@ -952,13 +952,10 @@ class Response(object):
cookie = self.cookie.output()
if cookie:
for line in cookie.split("\n"):
if line.endswith("\r"):
# Python 2.4 emits cookies joined by LF but 2.5+ by CRLF.
line = line[:-1]
name, value = line.split(": ", 1)
for line in cookie.split('\r\n'):
name, value = line.split(': ', 1)
if isinstance(name, six.text_type):
name = name.encode("ISO-8859-1")
name = name.encode('ISO-8859-1')
if isinstance(value, six.text_type):
value = headers.encode(value)
h.append((name, value))

26
cherrypy/_cpserver.py

@ -3,8 +3,8 @@
import six
import cherrypy
from cherrypy.lib import attributes
from cherrypy._cpcompat import basestring
from cherrypy.lib.reprconf import attributes
from cherrypy._cpcompat import text_or_bytes
# We import * because we want to export check_port
# et al as attributes of this module.
@ -35,7 +35,7 @@ class Server(ServerAdapter):
if value == '':
raise ValueError("The empty string ('') is not an allowed value. "
"Use '0.0.0.0' instead to listen on all active "
"interfaces (INADDR_ANY).")
'interfaces (INADDR_ANY).')
self._socket_host = value
socket_host = property(
_get_socket_host,
@ -156,7 +156,7 @@ class Server(ServerAdapter):
if httpserver is None:
from cherrypy import _cpwsgi_server
httpserver = _cpwsgi_server.CPWSGIServer(self)
if isinstance(httpserver, basestring):
if isinstance(httpserver, text_or_bytes):
# Is anyone using this? Can I add an arg?
httpserver = attributes(httpserver)(self)
return httpserver, self.bind_addr
@ -180,7 +180,7 @@ class Server(ServerAdapter):
self.socket_file = None
self.socket_host = None
self.socket_port = None
elif isinstance(value, basestring):
elif isinstance(value, text_or_bytes):
self.socket_file = value
self.socket_host = None
self.socket_port = None
@ -189,9 +189,9 @@ class Server(ServerAdapter):
self.socket_host, self.socket_port = value
self.socket_file = None
except ValueError:
raise ValueError("bind_addr must be a (host, port) tuple "
"(for TCP sockets) or a string (for Unix "
"domain sockets), not %r" % value)
raise ValueError('bind_addr must be a (host, port) tuple '
'(for TCP sockets) or a string (for Unix '
'domain sockets), not %r' % value)
bind_addr = property(
_get_bind_addr,
_set_bind_addr,
@ -215,12 +215,12 @@ class Server(ServerAdapter):
port = self.socket_port
if self.ssl_certificate:
scheme = "https"
scheme = 'https'
if port != 443:
host += ":%s" % port
host += ':%s' % port
else:
scheme = "http"
scheme = 'http'
if port != 80:
host += ":%s" % port
host += ':%s' % port
return "%s://%s" % (scheme, host)
return '%s://%s' % (scheme, host)

241
cherrypy/_cpthreadinglocal.py

@ -1,241 +0,0 @@
# This is a backport of Python-2.4's threading.local() implementation
"""Thread-local objects
(Note that this module provides a Python version of thread
threading.local class. Depending on the version of Python you're
using, there may be a faster one available. You should always import
the local class from threading.)
Thread-local objects support the management of thread-local data.
If you have data that you want to be local to a thread, simply create
a thread-local object and use its attributes:
>>> mydata = local()
>>> mydata.number = 42
>>> mydata.number
42
You can also access the local-object's dictionary:
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]
What's important about thread-local objects is that their data are
local to a thread. If we access the data in a different thread:
>>> log = []
>>> def f():
... items = mydata.__dict__.items()
... items.sort()
... log.append(items)
... mydata.number = 11
... log.append(mydata.number)
>>> import threading
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
>>> log
[[], 11]
we get different data. Furthermore, changes made in the other thread
don't affect data seen in this thread:
>>> mydata.number
42
Of course, values you get from a local object, including a __dict__
attribute, are for whatever thread was current at the time the
attribute was read. For that reason, you generally don't want to save
these values across threads, as they apply only to the thread they
came from.
You can create custom local objects by subclassing the local class:
>>> class MyLocal(local):
... number = 2
... initialized = False
... def __init__(self, **kw):
... if self.initialized:
... raise SystemError('__init__ called too many times')
... self.initialized = True
... self.__dict__.update(kw)
... def squared(self):
... return self.number ** 2
This can be useful to support default values, methods and
initialization. Note that if you define an __init__ method, it will be
called each time the local object is used in a separate thread. This
is necessary to initialize each thread's dictionary.
Now if we create a local object:
>>> mydata = MyLocal(color='red')
Now we have a default number:
>>> mydata.number
2
an initial color:
>>> mydata.color
'red'
>>> del mydata.color
And a method that operates on the data:
>>> mydata.squared()
4
As before, we can access the data in a separate thread:
>>> log = []
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
>>> log
[[('color', 'red'), ('initialized', True)], 11]
without affecting this thread's data:
>>> mydata.number
2
>>> mydata.color
Traceback (most recent call last):
...
AttributeError: 'MyLocal' object has no attribute 'color'
Note that subclasses can define slots, but they are not thread
local. They are shared across threads:
>>> class MyLocal(local):
... __slots__ = 'number'
>>> mydata = MyLocal()
>>> mydata.number = 42
>>> mydata.color = 'red'
So, the separate thread:
>>> thread = threading.Thread(target=f)
>>> thread.start()
>>> thread.join()
affects what we see:
>>> mydata.number
11
>>> del mydata
"""
# Threading import is at end
class _localbase(object):
__slots__ = '_local__key', '_local__args', '_local__lock'
def __new__(cls, *args, **kw):
self = object.__new__(cls)
key = 'thread.local.' + str(id(self))
object.__setattr__(self, '_local__key', key)
object.__setattr__(self, '_local__args', (args, kw))
object.__setattr__(self, '_local__lock', RLock())
if args or kw and (cls.__init__ is object.__init__):
raise TypeError("Initialization arguments are not supported")
# We need to create the thread dict in anticipation of
# __init__ being called, to make sure we don't call it
# again ourselves.
dict = object.__getattribute__(self, '__dict__')
currentThread().__dict__[key] = dict
return self
def _patch(self):
key = object.__getattribute__(self, '_local__key')
d = currentThread().__dict__.get(key)
if d is None:
d = {}
currentThread().__dict__[key] = d
object.__setattr__(self, '__dict__', d)
# we have a new instance dict, so call out __init__ if we have
# one
cls = type(self)
if cls.__init__ is not object.__init__:
args, kw = object.__getattribute__(self, '_local__args')
cls.__init__(self, *args, **kw)
else:
object.__setattr__(self, '__dict__', d)
class local(_localbase):
def __getattribute__(self, name):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__getattribute__(self, name)
finally:
lock.release()
def __setattr__(self, name, value):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__setattr__(self, name, value)
finally:
lock.release()
def __delattr__(self, name):
lock = object.__getattribute__(self, '_local__lock')
lock.acquire()
try:
_patch(self)
return object.__delattr__(self, name)
finally:
lock.release()
def __del__():
threading_enumerate = enumerate
__getattribute__ = object.__getattribute__
def __del__(self):
key = __getattribute__(self, '_local__key')
try:
threads = list(threading_enumerate())
except:
# if enumerate fails, as it seems to do during
# shutdown, we'll skip cleanup under the assumption
# that there is nothing to clean up
return
for thread in threads:
try:
__dict__ = thread.__dict__
except AttributeError:
# Thread is dying, rest in peace
continue
if key in __dict__:
try:
del __dict__[key]
except KeyError:
pass # didn't have anything in this thread
return __del__
__del__ = __del__()
from threading import currentThread, enumerate, RLock

73
cherrypy/_cptools.py

@ -28,6 +28,11 @@ import warnings
import cherrypy
from cherrypy._helper import expose
from cherrypy.lib import cptools, encoding, auth, static, jsontools
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
def _getargs(func):
"""Return the names of all static arguments to the given function."""
@ -45,8 +50,8 @@ def _getargs(func):
_attr_error = (
"CherryPy Tools cannot be turned on directly. Instead, turn them "
"on via config, or use them as decorators on your page handlers."
'CherryPy Tools cannot be turned on directly. Instead, turn them '
'on via config, or use them as decorators on your page handlers.'
)
@ -57,7 +62,7 @@ class Tool(object):
help(tool.callable) should give you more information about this Tool.
"""
namespace = "tools"
namespace = 'tools'
def __init__(self, point, callable, name=None, priority=50):
self._point = point
@ -80,7 +85,7 @@ class Tool(object):
for arg in _getargs(self.callable):
setattr(self, arg, None)
except (TypeError, AttributeError):
if hasattr(self.callable, "__call__"):
if hasattr(self.callable, '__call__'):
for arg in _getargs(self.callable.__call__):
setattr(self, arg, None)
# IronPython 1.0 raises NotImplementedError because
@ -104,8 +109,8 @@ class Tool(object):
if self._name in tm:
conf.update(tm[self._name])
if "on" in conf:
del conf["on"]
if 'on' in conf:
del conf['on']
return conf
@ -120,15 +125,15 @@ class Tool(object):
return cherrypy.request.base
"""
if args:
raise TypeError("The %r Tool does not accept positional "
"arguments; you must use keyword arguments."
raise TypeError('The %r Tool does not accept positional '
'arguments; you must use keyword arguments.'
% self._name)
def tool_decorator(f):
if not hasattr(f, "_cp_config"):
if not hasattr(f, '_cp_config'):
f._cp_config = {}
subspace = self.namespace + "." + self._name + "."
f._cp_config[subspace + "on"] = True
subspace = self.namespace + '.' + self._name + '.'
f._cp_config[subspace + 'on'] = True
for k, v in kwargs.items():
f._cp_config[subspace + k] = v
return f
@ -141,9 +146,9 @@ class Tool(object):
method when the tool is "turned on" in config.
"""
conf = self._merged_args()
p = conf.pop("priority", None)
p = conf.pop('priority', None)
if p is None:
p = getattr(self.callable, "priority", self._priority)
p = getattr(self.callable, 'priority', self._priority)
cherrypy.serving.request.hooks.attach(self._point, self.callable,
priority=p, **conf)
@ -191,9 +196,9 @@ class HandlerTool(Tool):
method when the tool is "turned on" in config.
"""
conf = self._merged_args()
p = conf.pop("priority", None)
p = conf.pop('priority', None)
if p is None:
p = getattr(self.callable, "priority", self._priority)
p = getattr(self.callable, 'priority', self._priority)
cherrypy.serving.request.hooks.attach(self._point, self._wrapper,
priority=p, **conf)
@ -254,11 +259,6 @@ class ErrorTool(Tool):
# Builtin tools #
from cherrypy.lib import cptools, encoding, auth, static, jsontools
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
class SessionTool(Tool):
@ -296,9 +296,9 @@ class SessionTool(Tool):
conf = self._merged_args()
p = conf.pop("priority", None)
p = conf.pop('priority', None)
if p is None:
p = getattr(self.callable, "priority", self._priority)
p = getattr(self.callable, 'priority', self._priority)
hooks.attach(self._point, self.callable, priority=p, **conf)
@ -374,7 +374,7 @@ class XMLRPCController(object):
for attr in str(rpcmethod).split('.'):
subhandler = getattr(subhandler, attr, None)
if subhandler and getattr(subhandler, "exposed", False):
if subhandler and getattr(subhandler, 'exposed', False):
body = subhandler(*(vpath + rpcparams), **params)
else:
@ -384,7 +384,7 @@ class XMLRPCController(object):
# cherrypy.lib.xmlrpcutil.on_error
raise Exception('method "%s" is not supported' % attr)
conf = cherrypy.serving.request.toolmaps['tools'].get("xmlrpc", {})
conf = cherrypy.serving.request.toolmaps['tools'].get('xmlrpc', {})
_xmlrpc.respond(body,
conf.get('encoding', 'utf-8'),
conf.get('allow_none', 0))
@ -395,7 +395,7 @@ class SessionAuthTool(HandlerTool):
def _setargs(self):
for name in dir(cptools.SessionAuth):
if not name.startswith("__"):
if not name.startswith('__'):
setattr(self, name, None)
@ -418,7 +418,7 @@ class CachingTool(Tool):
"""Hook caching into cherrypy.request."""
conf = self._merged_args()
p = conf.pop("priority", None)
p = conf.pop('priority', None)
cherrypy.serving.request.hooks.attach('before_handler', self._wrapper,
priority=p, **conf)
@ -447,7 +447,7 @@ class Toolbox(object):
cherrypy.serving.request.toolmaps[self.namespace] = map = {}
def populate(k, v):
toolname, arg = k.split(".", 1)
toolname, arg = k.split('.', 1)
bucket = map.setdefault(toolname, {})
bucket[arg] = v
return populate
@ -457,7 +457,7 @@ class Toolbox(object):
map = cherrypy.serving.request.toolmaps.get(self.namespace)
if map:
for name, settings in map.items():
if settings.get("on", False):
if settings.get('on', False):
tool = getattr(self, name)
tool._setup()
@ -472,7 +472,7 @@ class Toolbox(object):
class DeprecatedTool(Tool):
_name = None
warnmsg = "This Tool is deprecated."
warnmsg = 'This Tool is deprecated.'
def __init__(self, point, warnmsg=None):
self.point = point
@ -490,7 +490,7 @@ class DeprecatedTool(Tool):
warnings.warn(self.warnmsg)
default_toolbox = _d = Toolbox("tools")
default_toolbox = _d = Toolbox('tools')
_d.session_auth = SessionAuthTool(cptools.session_auth)
_d.allow = Tool('on_start_resource', cptools.allow)
_d.proxy = Tool('before_request_body', cptools.proxy, priority=30)
@ -512,14 +512,14 @@ _d.caching = CachingTool('before_handler', _caching.get, 'caching')
_d.expires = Tool('before_finalize', _caching.expires)
_d.tidy = DeprecatedTool(
'before_finalize',
"The tidy tool has been removed from the standard distribution of "
"CherryPy. The most recent version can be found at "
"http://tools.cherrypy.org/browser.")
'The tidy tool has been removed from the standard distribution of '
'CherryPy. The most recent version can be found at '
'http://tools.cherrypy.org/browser.')
_d.nsgmls = DeprecatedTool(
'before_finalize',
"The nsgmls tool has been removed from the standard distribution of "
"CherryPy. The most recent version can be found at "
"http://tools.cherrypy.org/browser.")
'The nsgmls tool has been removed from the standard distribution of '
'CherryPy. The most recent version can be found at '
'http://tools.cherrypy.org/browser.')
_d.ignore_headers = Tool('before_request_body', cptools.ignore_headers)
_d.referer = Tool('before_request_body', cptools.referer)
_d.basic_auth = Tool('on_start_resource', auth.basic_auth)
@ -533,5 +533,6 @@ _d.json_in = Tool('before_request_body', jsontools.json_in, priority=30)
_d.json_out = Tool('before_handler', jsontools.json_out, priority=30)
_d.auth_basic = Tool('before_handler', auth_basic.basic_auth, priority=1)
_d.auth_digest = Tool('before_handler', auth_digest.digest_auth, priority=1)
_d.params = Tool('before_handler', cptools.convert_params)
del _d, cptools, encoding, auth, static

60
cherrypy/_cptree.py

@ -46,22 +46,22 @@ class Application(object):
relative_urls = False
def __init__(self, root, script_name="", config=None):
def __init__(self, root, script_name='', config=None):
self.log = _cplogging.LogManager(id(self), cherrypy.log.logger_root)
self.root = root
self.script_name = script_name
self.wsgiapp = _cpwsgi.CPWSGIApp(self)
self.namespaces = self.namespaces.copy()
self.namespaces["log"] = lambda k, v: setattr(self.log, k, v)
self.namespaces["wsgi"] = self.wsgiapp.namespace_handler
self.namespaces['log'] = lambda k, v: setattr(self.log, k, v)
self.namespaces['wsgi'] = self.wsgiapp.namespace_handler
self.config = self.__class__.config.copy()
if config:
self.merge(config)
def __repr__(self):
return "%s.%s(%r, %r)" % (self.__module__, self.__class__.__name__,
return '%s.%s(%r, %r)' % (self.__module__, self.__class__.__name__,
self.root, self.script_name)
script_name_doc = """The URI "mount point" for this app. A mount point
@ -86,11 +86,11 @@ class Application(object):
# A `_script_name` with a value of None signals that the script name
# should be pulled from WSGI environ.
return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip("/")
return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip('/')
def _set_script_name(self, value):
if value:
value = value.rstrip("/")
value = value.rstrip('/')
self._script_name = value
script_name = property(fget=_get_script_name, fset=_set_script_name,
doc=script_name_doc)
@ -100,22 +100,22 @@ class Application(object):
_cpconfig.merge(self.config, config)
# Handle namespaces specified in config.
self.namespaces(self.config.get("/", {}))
self.namespaces(self.config.get('/', {}))
def find_config(self, path, key, default=None):
"""Return the most-specific value for key along path, or default."""
trail = path or "/"
trail = path or '/'
while trail:
nodeconf = self.config.get(trail, {})
if key in nodeconf:
return nodeconf[key]
lastslash = trail.rfind("/")
lastslash = trail.rfind('/')
if lastslash == -1:
break
elif lastslash == 0 and trail != "/":
trail = "/"
elif lastslash == 0 and trail != '/':
trail = '/'
else:
trail = trail[:lastslash]
@ -172,7 +172,7 @@ class Tree(object):
def __init__(self):
self.apps = {}
def mount(self, root, script_name="", config=None):
def mount(self, root, script_name='', config=None):
"""Mount a new app from a root object, script_name, and config.
root
@ -197,29 +197,29 @@ class Tree(object):
if script_name is None:
raise TypeError(
"The 'script_name' argument may not be None. Application "
"objects may, however, possess a script_name of None (in "
"order to inpect the WSGI environ for SCRIPT_NAME upon each "
"request). You cannot mount such Applications on this Tree; "
"you must pass them to a WSGI server interface directly.")
'objects may, however, possess a script_name of None (in '
'order to inpect the WSGI environ for SCRIPT_NAME upon each '
'request). You cannot mount such Applications on this Tree; '
'you must pass them to a WSGI server interface directly.')
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
script_name = script_name.rstrip("/")
script_name = script_name.rstrip('/')
if isinstance(root, Application):
app = root
if script_name != "" and script_name != app.script_name:
if script_name != '' and script_name != app.script_name:
raise ValueError(
"Cannot specify a different script name and pass an "
"Application instance to cherrypy.mount")
'Cannot specify a different script name and pass an '
'Application instance to cherrypy.mount')
script_name = app.script_name
else:
app = Application(root, script_name)
# If mounted at "", add favicon.ico
if (script_name == "" and root is not None
and not hasattr(root, "favicon_ico")):
if (script_name == '' and root is not None
and not hasattr(root, 'favicon_ico')):
favicon = os.path.join(os.getcwd(), os.path.dirname(__file__),
"favicon.ico")
'favicon.ico')
root.favicon_ico = tools.staticfile.handler(favicon)
if config:
@ -229,10 +229,10 @@ class Tree(object):
return app
def graft(self, wsgi_callable, script_name=""):
def graft(self, wsgi_callable, script_name=''):
"""Mount a wsgi callable at the given script_name."""
# Next line both 1) strips trailing slash and 2) maps "/" -> "".
script_name = script_name.rstrip("/")
script_name = script_name.rstrip('/')
self.apps[script_name] = wsgi_callable
def script_name(self, path=None):
@ -252,11 +252,11 @@ class Tree(object):
if path in self.apps:
return path
if path == "":
if path == '':
return None
# Move one node up the tree and try again.
path = path[:path.rfind("/")]
path = path[:path.rfind('/')]
def __call__(self, environ, start_response):
# If you're calling this, then you're probably setting SCRIPT_NAME
@ -267,7 +267,7 @@ class Tree(object):
env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ)
path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''),
env1x.get('PATH_INFO', ''))
sn = self.script_name(path or "/")
sn = self.script_name(path or '/')
if sn is None:
start_response('404 Not Found', [])
return []
@ -280,8 +280,8 @@ class Tree(object):
# 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)
environ[ntou('PATH_INFO')] = path[len(sn.rstrip('/')):].decode(enc)
else:
environ['SCRIPT_NAME'] = sn
environ['PATH_INFO'] = path[len(sn.rstrip("/")):]
environ['PATH_INFO'] = path[len(sn.rstrip('/')):]
return app(environ, start_response)

166
cherrypy/_cpwsgi.py

@ -46,10 +46,13 @@ class VirtualHost(object):
Domain2App = cherrypy.Application(root)
SecureApp = cherrypy.Application(Secure())
vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
domains={'www.domain2.example': Domain2App,
'www.domain2.example:443': SecureApp,
})
vhost = cherrypy._cpwsgi.VirtualHost(
RootApp,
domains={
'www.domain2.example': Domain2App,
'www.domain2.example:443': SecureApp,
},
)
cherrypy.tree.graft(vhost)
"""
@ -78,7 +81,7 @@ class VirtualHost(object):
def __call__(self, environ, start_response):
domain = environ.get('HTTP_HOST', '')
if self.use_x_forwarded_host:
domain = environ.get("HTTP_X_FORWARDED_HOST", domain)
domain = environ.get('HTTP_X_FORWARDED_HOST', domain)
nextapp = self.domains.get(domain)
if nextapp is None:
@ -109,7 +112,7 @@ class InternalRedirector(object):
# Add the *previous* path_info + qs to redirections.
old_uri = sn + path
if qs:
old_uri += "?" + qs
old_uri += '?' + qs
redirections.append(old_uri)
if not self.recursive:
@ -117,18 +120,20 @@ class InternalRedirector(object):
# already
new_uri = sn + ir.path
if ir.query_string:
new_uri += "?" + ir.query_string
new_uri += '?' + ir.query_string
if new_uri in redirections:
ir.request.close()
raise RuntimeError("InternalRedirector visited the "
"same URL twice: %r" % new_uri)
tmpl = (
'InternalRedirector visited the same URL twice: %r'
)
raise RuntimeError(tmpl % new_uri)
# Munge the environment and try again.
environ['REQUEST_METHOD'] = "GET"
environ['REQUEST_METHOD'] = 'GET'
environ['PATH_INFO'] = ir.path
environ['QUERY_STRING'] = ir.query_string
environ['wsgi.input'] = io.BytesIO()
environ['CONTENT_LENGTH'] = "0"
environ['CONTENT_LENGTH'] = '0'
environ['cherrypy.previous_request'] = ir.request
@ -160,7 +165,8 @@ class _TrappedResponse(object):
self.throws = throws
self.started_response = False
self.response = self.trap(
self.nextapp, self.environ, self.start_response)
self.nextapp, self.environ, self.start_response,
)
self.iter_response = iter(self.response)
def __iter__(self):
@ -187,16 +193,17 @@ class _TrappedResponse(object):
raise
except:
tb = _cperror.format_exc()
#print('trapped (started %s):' % self.started_response, tb)
_cherrypy.log(tb, severity=40)
if not _cherrypy.request.show_tracebacks:
tb = ""
tb = ''
s, h, b = _cperror.bare_error(tb)
if six.PY3:
# What fun.
s = s.decode('ISO-8859-1')
h = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
for k, v in h]
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([])
@ -215,7 +222,7 @@ class _TrappedResponse(object):
raise
if self.started_response:
return ntob("").join(b)
return ntob('').join(b)
else:
return b
@ -240,18 +247,18 @@ class AppResponse(object):
outstatus = r.output_status
if not isinstance(outstatus, bytes):
raise TypeError("response.output_status is not a byte string.")
raise TypeError('response.output_status is not a byte string.')
outheaders = []
for k, v in r.header_list:
if not isinstance(k, bytes):
raise TypeError(
"response.header_list key %r is not a byte string." %
k)
tmpl = 'response.header_list key %r is not a byte string.'
raise TypeError(tmpl % k)
if not isinstance(v, bytes):
raise TypeError(
"response.header_list value %r is not a byte string." %
v)
tmpl = (
'response.header_list value %r is not a byte string.'
)
raise TypeError(tmpl % v)
outheaders.append((k, v))
if six.PY3:
@ -260,8 +267,10 @@ class AppResponse(object):
# 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]
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)
@ -299,14 +308,18 @@ class AppResponse(object):
"""Create a Request object using environ."""
env = self.environ.get
local = httputil.Host('',
int(env('SERVER_PORT', 80) or -1),
env('SERVER_NAME', ''))
remote = httputil.Host(env('REMOTE_ADDR', ''),
int(env('REMOTE_PORT', -1) or -1),
env('REMOTE_HOST', ''))
local = httputil.Host(
'',
int(env('SERVER_PORT', 80) or -1),
env('SERVER_NAME', ''),
)
remote = httputil.Host(
env('REMOTE_ADDR', ''),
int(env('REMOTE_PORT', -1) or -1),
env('REMOTE_HOST', ''),
)
scheme = env('wsgi.url_scheme')
sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
sproto = env('ACTUAL_SERVER_PROTOCOL', 'HTTP/1.1')
request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
# LOGON_USER is served by IIS, and is the name of the
@ -320,44 +333,54 @@ class AppResponse(object):
meth = self.environ['REQUEST_METHOD']
path = httputil.urljoin(self.environ.get('SCRIPT_NAME', ''),
self.environ.get('PATH_INFO', ''))
path = httputil.urljoin(
self.environ.get('SCRIPT_NAME', ''),
self.environ.get('PATH_INFO', ''),
)
qs = self.environ.get('QUERY_STRING', '')
if six.PY3:
# 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
path, qs = self.recode_path_qs(path, qs) or (path, qs)
rproto = self.environ.get('SERVER_PROTOCOL')
headers = self.translate_headers(self.environ)
rfile = self.environ['wsgi.input']
request.run(meth, path, qs, rproto, headers, rfile)
headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
'CONTENT_LENGTH': 'Content-Length',
'CONTENT_TYPE': 'Content-Type',
'REMOTE_HOST': 'Remote-Host',
'REMOTE_ADDR': 'Remote-Addr',
}
headerNames = {
'HTTP_CGI_AUTHORIZATION': 'Authorization',
'CONTENT_LENGTH': 'Content-Length',
'CONTENT_TYPE': 'Content-Type',
'REMOTE_HOST': 'Remote-Host',
'REMOTE_ADDR': 'Remote-Addr',
}
def recode_path_qs(self, path, qs):
if not six.PY3:
return
# 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():
return
# 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:
return (
path.encode(old_enc).decode(new_enc),
qs.encode(old_enc).decode(new_enc),
)
except (UnicodeEncodeError, UnicodeDecodeError):
# Just pass them through without transcoding and hope.
pass
def translate_headers(self, environ):
"""Translate CGI-environ header names to HTTP header names."""
@ -365,9 +388,9 @@ class AppResponse(object):
# We assume all incoming header keys are uppercase already.
if cgiName in self.headerNames:
yield self.headerNames[cgiName], environ[cgiName]
elif cgiName[:5] == "HTTP_":
elif cgiName[:5] == 'HTTP_':
# Hackish attempt at recovering original header names.
translatedHeader = cgiName[5:].replace("_", "-")
translatedHeader = cgiName[5:].replace('_', '-')
yield translatedHeader, environ[cgiName]
@ -375,9 +398,10 @@ class CPWSGIApp(object):
"""A WSGI application object for a CherryPy Application."""
pipeline = [('ExceptionTrapper', ExceptionTrapper),
('InternalRedirector', InternalRedirector),
]
pipeline = [
('ExceptionTrapper', ExceptionTrapper),
('InternalRedirector', InternalRedirector),
]
"""A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
constructor that takes an initial, positional 'nextapp' argument,
plus optional keyword arguments, and returns a WSGI application
@ -427,16 +451,16 @@ class CPWSGIApp(object):
def namespace_handler(self, k, v):
"""Config handler for the 'wsgi' namespace."""
if k == "pipeline":
if k == 'pipeline':
# Note this allows multiple 'wsgi.pipeline' config entries
# (but each entry will be processed in a 'random' order).
# It should also allow developers to set default middleware
# in code (passed to self.__init__) that deployers can add to
# (but not remove) via config.
self.pipeline.extend(v)
elif k == "response_class":
elif k == 'response_class':
self.response_class = v
else:
name, arg = k.split(".", 1)
name, arg = k.split('.', 1)
bucket = self.config.setdefault(name, {})
bucket[arg] = v

2
cherrypy/_cpwsgi_server.py

@ -66,5 +66,5 @@ class CPWSGIServer(wsgiserver.CherryPyWSGIServer):
self.stats['Enabled'] = getattr(
self.server_adapter, 'statistics', False)
def error_log(self, msg="", level=20, traceback=False):
def error_log(self, msg='', level=20, traceback=False):
cherrypy.engine.log(msg, level, traceback)

18
cherrypy/_helper.py

@ -5,7 +5,7 @@ Helper functions for CP apps
import six
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
from cherrypy._cpcompat import basestring
from cherrypy._cpcompat import text_or_bytes
import cherrypy
@ -17,11 +17,11 @@ def expose(func=None, alias=None):
def expose_(func):
func.exposed = True
if alias is not None:
if isinstance(alias, basestring):
parents[alias.replace(".", "_")] = func
if isinstance(alias, text_or_bytes):
parents[alias.replace('.', '_')] = func
else:
for a in alias:
parents[a.replace(".", "_")] = func
parents[a.replace('.', '_')] = func
return func
import sys
@ -191,7 +191,7 @@ def popargs(*args, **kwargs):
return decorated
def url(path="", qs="", script_name=None, base=None, relative=None):
def url(path='', qs='', script_name=None, base=None, relative=None):
"""Create an absolute URL for the given path.
If 'path' starts with a slash ('/'), this will return
@ -224,7 +224,7 @@ def url(path="", qs="", script_name=None, base=None, relative=None):
qs = '?' + qs
if cherrypy.request.app:
if not path.startswith("/"):
if not path.startswith('/'):
# Append/remove trailing slash from path_info as needed
# (this is to support mistyped URL's without redirecting;
# if you want to redirect, use tools.trailing_slash).
@ -236,7 +236,7 @@ def url(path="", qs="", script_name=None, base=None, relative=None):
if pi.endswith('/') and pi != '/':
pi = pi[:-1]
if path == "":
if path == '':
path = pi
else:
path = _urljoin(pi, path)
@ -255,7 +255,7 @@ def url(path="", qs="", script_name=None, base=None, relative=None):
if base is None:
base = cherrypy.server.base()
path = (script_name or "") + path
path = (script_name or '') + path
newurl = base + path + qs
if './' in newurl:
@ -273,7 +273,7 @@ def url(path="", qs="", script_name=None, base=None, relative=None):
# At this point, we should have a fully-qualified absolute URL.
if relative is None:
relative = getattr(cherrypy.request.app, "relative_urls", False)
relative = getattr(cherrypy.request.app, 'relative_urls', False)
# See http://www.ietf.org/rfc/rfc2396.txt
if relative == 'server':

46
cherrypy/daemon.py

@ -13,7 +13,7 @@ def start(configfiles=None, daemonize=False, environment=None,
"""Subscribe all engine plugins and start the engine."""
sys.path = [''] + sys.path
for i in imports or []:
exec("import %s" % i)
exec('import %s' % i)
for c in configfiles or []:
cherrypy.config.update(c)
@ -37,18 +37,18 @@ def start(configfiles=None, daemonize=False, environment=None,
if pidfile:
plugins.PIDFile(engine, pidfile).subscribe()
if hasattr(engine, "signal_handler"):
if hasattr(engine, 'signal_handler'):
engine.signal_handler.subscribe()
if hasattr(engine, "console_control_handler"):
if hasattr(engine, 'console_control_handler'):
engine.console_control_handler.subscribe()
if (fastcgi and (scgi or cgi)) or (scgi and cgi):
cherrypy.log.error("You may only specify one of the cgi, fastcgi, and "
"scgi options.", 'ENGINE')
cherrypy.log.error('You may only specify one of the cgi, fastcgi, and '
'scgi options.', 'ENGINE')
sys.exit(1)
elif fastcgi or scgi or cgi:
# Turn off autoreload when using *cgi.
cherrypy.config.update({'engine.autoreload_on': False})
cherrypy.config.update({'engine.autoreload.on': False})
# Turn off the default HTTP server (which is subscribed by default).
cherrypy.server.unsubscribe()
@ -76,25 +76,25 @@ def run():
from optparse import OptionParser
p = OptionParser()
p.add_option('-c', '--config', action="append", dest='config',
help="specify config file(s)")
p.add_option('-d', action="store_true", dest='daemonize',
help="run the server as a daemon")
p.add_option('-c', '--config', action='append', dest='config',
help='specify config file(s)')
p.add_option('-d', action='store_true', dest='daemonize',
help='run the server as a daemon')
p.add_option('-e', '--environment', dest='environment', default=None,
help="apply the given config environment")
p.add_option('-f', action="store_true", dest='fastcgi',
help="start a fastcgi server instead of the default HTTP "
"server")
p.add_option('-s', action="store_true", dest='scgi',
help="start a scgi server instead of the default HTTP server")
p.add_option('-x', action="store_true", dest='cgi',
help="start a cgi server instead of the default HTTP server")
p.add_option('-i', '--import', action="append", dest='imports',
help="specify modules to import")
help='apply the given config environment')
p.add_option('-f', action='store_true', dest='fastcgi',
help='start a fastcgi server instead of the default HTTP '
'server')
p.add_option('-s', action='store_true', dest='scgi',
help='start a scgi server instead of the default HTTP server')
p.add_option('-x', action='store_true', dest='cgi',
help='start a cgi server instead of the default HTTP server')
p.add_option('-i', '--import', action='append', dest='imports',
help='specify modules to import')
p.add_option('-p', '--pidfile', dest='pidfile', default=None,
help="store the process id in the given file")
p.add_option('-P', '--Path', action="append", dest='Path',
help="add the given paths to sys.path")
help='store the process id in the given file')
p.add_option('-P', '--Path', action='append', dest='Path',
help='add the given paths to sys.path')
options, args = p.parse_args()
if options.Path:

22
cherrypy/lib/__init__.py

@ -1,7 +1,5 @@
"""CherryPy Library"""
# Deprecated in CherryPy 3.2 -- remove in CherryPy 3.3
from cherrypy.lib.reprconf import unrepr, modules, attributes
def is_iterator(obj):
'''Returns a boolean indicating if the object provided implements
@ -16,22 +14,23 @@ def is_iterator(obj):
# Types which implement the protocol must return themselves when
# invoking 'iter' upon them.
return iter(obj) is obj
def is_closable_iterator(obj):
# Not an iterator.
if not is_iterator(obj):
return False
# A generator - the easiest thing to deal with.
import inspect
if inspect.isgenerator(obj):
return True
# A custom iterator. Look for a close method...
if not (hasattr(obj, 'close') and callable(obj.close)):
return False
# ... which doesn't require any arguments.
try:
inspect.getcallargs(obj.close)
@ -40,6 +39,7 @@ def is_closable_iterator(obj):
else:
return True
class file_generator(object):
"""Yield the given input (a file object) in chunks (default 64k). (Core)"""
@ -77,9 +77,9 @@ def file_generator_limited(fileobj, count, chunk_size=65536):
def set_vary_header(response, header_name):
"Add a Vary header to a response"
varies = response.headers.get("Vary", "")
varies = [x.strip() for x in varies.split(",") if x.strip()]
'Add a Vary header to a response'
varies = response.headers.get('Vary', '')
varies = [x.strip() for x in varies.split(',') if x.strip()]
if header_name not in varies:
varies.append(header_name)
response.headers['Vary'] = ", ".join(varies)
response.headers['Vary'] = ', '.join(varies)

16
cherrypy/lib/auth.py

@ -22,25 +22,25 @@ def check_auth(users, encrypt=None, realm=None):
if not isinstance(users, dict):
raise ValueError(
"Authentication users must be a dictionary")
'Authentication users must be a dictionary')
# fetch the user password
password = users.get(ah["username"], None)
password = users.get(ah['username'], None)
except TypeError:
# returns a password (encrypted or clear text)
password = users(ah["username"])
password = users(ah['username'])
else:
if not isinstance(users, dict):
raise ValueError("Authentication users must be a dictionary")
raise ValueError('Authentication users must be a dictionary')
# fetch the user password
password = users.get(ah["username"], None)
password = users.get(ah['username'], None)
# validate the authorization by re-computing it here
# and compare it with what the user-agent provided
if httpauth.checkResponse(ah, password, method=request.method,
encrypt=encrypt, realm=realm):
request.login = ah["username"]
request.login = ah['username']
return True
request.login = False
@ -72,7 +72,7 @@ def basic_auth(realm, users, encrypt=None, debug=False):
'www-authenticate'] = httpauth.basicAuth(realm)
raise cherrypy.HTTPError(
401, "You are not authorized to access that resource")
401, 'You are not authorized to access that resource')
def digest_auth(realm, users, debug=False):
@ -94,4 +94,4 @@ def digest_auth(realm, users, debug=False):
'www-authenticate'] = httpauth.digestAuth(realm)
raise cherrypy.HTTPError(
401, "You are not authorized to access that resource")
401, 'You are not authorized to access that resource')

18
cherrypy/lib/auth_basic.py

@ -2,6 +2,12 @@
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
import binascii
import cherrypy
from cherrypy._cpcompat import base64_decode
__doc__ = """This module provides a CherryPy 3.x tool which implements
the server-side of HTTP Basic Access Authentication, as described in
:rfc:`2617`.
@ -22,10 +28,6 @@ as the credentials store::
__author__ = 'visteya'
__date__ = 'April 2009'
import binascii
from cherrypy._cpcompat import base64_decode
import cherrypy
def checkpassword_dict(user_password_dict):
"""Returns a checkpassword function which checks credentials
@ -70,7 +72,8 @@ def basic_auth(realm, checkpassword, debug=False):
auth_header = request.headers.get('authorization')
if auth_header is not None:
try:
# split() error, base64.decodestring() error
with cherrypy.HTTPError.handle((ValueError, binascii.Error), 400, 'Bad Request'):
scheme, params = auth_header.split(' ', 1)
if scheme.lower() == 'basic':
username, password = base64_decode(params).split(':', 1)
@ -79,12 +82,9 @@ def basic_auth(realm, checkpassword, debug=False):
cherrypy.log('Auth succeeded', 'TOOLS.AUTH_BASIC')
request.login = username
return # successful authentication
# split() error, base64.decodestring() error
except (ValueError, binascii.Error):
raise cherrypy.HTTPError(400, 'Bad Request')
# Respond with 401 status and a WWW-Authenticate header
cherrypy.serving.response.headers[
'www-authenticate'] = 'Basic realm="%s"' % realm
raise cherrypy.HTTPError(
401, "You are not authorized to access that resource")
401, 'You are not authorized to access that resource')

51
cherrypy/lib/auth_digest.py

@ -2,6 +2,13 @@
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
import time
from hashlib import md5
import cherrypy
from cherrypy._cpcompat import ntob, parse_http_list, parse_keqv_list
__doc__ = """An implementation of the server-side of HTTP Digest Access
Authentication, which is described in :rfc:`2617`.
@ -22,12 +29,6 @@ __author__ = 'visteya'
__date__ = 'April 2009'
import time
from hashlib import md5
from cherrypy._cpcompat import parse_http_list, parse_keqv_list
import cherrypy
from cherrypy._cpcompat import ntob
md5_hex = lambda s: md5(ntob(s)).hexdigest()
qop_auth = 'auth'
@ -142,7 +143,7 @@ class HttpDigestAuthorization (object):
def __init__(self, auth_header, http_method, debug=False):
self.http_method = http_method
self.debug = debug
scheme, params = auth_header.split(" ", 1)
scheme, params = auth_header.split(' ', 1)
self.scheme = scheme.lower()
if self.scheme != 'digest':
raise ValueError('Authorization scheme is not "Digest"')
@ -180,7 +181,7 @@ class HttpDigestAuthorization (object):
)
if not has_reqd:
raise ValueError(
self.errmsg("Not all required parameters are present."))
self.errmsg('Not all required parameters are present.'))
if self.qop:
if self.qop not in valid_qops:
@ -188,13 +189,13 @@ class HttpDigestAuthorization (object):
self.errmsg("Unsupported value for qop: '%s'" % self.qop))
if not (self.cnonce and self.nc):
raise ValueError(
self.errmsg("If qop is sent then "
"cnonce and nc MUST be present"))
self.errmsg('If qop is sent then '
'cnonce and nc MUST be present'))
else:
if self.cnonce or self.nc:
raise ValueError(
self.errmsg("If qop is not sent, "
"neither cnonce nor nc can be present"))
self.errmsg('If qop is not sent, '
'neither cnonce nor nc can be present'))
def __str__(self):
return 'authorization : %s' % self.auth_header
@ -239,7 +240,7 @@ class HttpDigestAuthorization (object):
except ValueError: # int() error
pass
if self.debug:
TRACE("nonce is stale")
TRACE('nonce is stale')
return True
def HA2(self, entity_body=''):
@ -251,14 +252,14 @@ class HttpDigestAuthorization (object):
#
# If the "qop" value is "auth-int", then A2 is:
# A2 = method ":" digest-uri-value ":" H(entity-body)
if self.qop is None or self.qop == "auth":
if self.qop is None or self.qop == 'auth':
a2 = '%s:%s' % (self.http_method, self.uri)
elif self.qop == "auth-int":
a2 = "%s:%s:%s" % (self.http_method, self.uri, H(entity_body))
elif self.qop == 'auth-int':
a2 = '%s:%s:%s' % (self.http_method, self.uri, H(entity_body))
else:
# in theory, this should never happen, since I validate qop in
# __init__()
raise ValueError(self.errmsg("Unrecognized value for qop!"))
raise ValueError(self.errmsg('Unrecognized value for qop!'))
return H(a2)
def request_digest(self, ha1, entity_body=''):
@ -279,10 +280,10 @@ class HttpDigestAuthorization (object):
ha2 = self.HA2(entity_body)
# Request-Digest -- RFC 2617 3.2.2.1
if self.qop:
req = "%s:%s:%s:%s:%s" % (
req = '%s:%s:%s:%s:%s' % (
self.nonce, self.nc, self.cnonce, self.qop, ha2)
else:
req = "%s:%s" % (self.nonce, ha2)
req = '%s:%s' % (self.nonce, ha2)
# RFC 2617 3.2.2.2
#
@ -351,12 +352,10 @@ def digest_auth(realm, get_ha1, key, debug=False):
auth_header = request.headers.get('authorization')
nonce_is_stale = False
if auth_header is not None:
try:
with cherrypy.HTTPError.handle(ValueError, 400,
'The Authorization header could not be parsed.'):
auth = HttpDigestAuthorization(
auth_header, request.method, debug=debug)
except ValueError:
raise cherrypy.HTTPError(
400, "The Authorization header could not be parsed.")
if debug:
TRACE(str(auth))
@ -370,7 +369,7 @@ def digest_auth(realm, get_ha1, key, debug=False):
digest = auth.request_digest(ha1, entity_body=request.body)
if digest == auth.response: # authenticated
if debug:
TRACE("digest matches auth.response")
TRACE('digest matches auth.response')
# Now check if nonce is stale.
# The choice of ten minutes' lifetime for nonce is somewhat
# arbitrary
@ -378,7 +377,7 @@ def digest_auth(realm, get_ha1, key, debug=False):
if not nonce_is_stale:
request.login = auth.username
if debug:
TRACE("authentication of %s successful" %
TRACE('authentication of %s successful' %
auth.username)
return
@ -388,4 +387,4 @@ def digest_auth(realm, get_ha1, key, debug=False):
TRACE(header)
cherrypy.serving.response.headers['WWW-Authenticate'] = header
raise cherrypy.HTTPError(
401, "You are not authorized to access that resource")
401, 'You are not authorized to access that resource')

26
cherrypy/lib/caching.py

@ -39,7 +39,7 @@ import time
import cherrypy
from cherrypy.lib import cptools, httputil
from cherrypy._cpcompat import copyitems, ntob, set_daemon, sorted, Event
from cherrypy._cpcompat import copyitems, ntob, sorted, Event
class Cache(object):
@ -170,7 +170,7 @@ class MemoryCache(Cache):
# Run self.expire_cache in a separate daemon thread.
t = threading.Thread(target=self.expire_cache, name='expire_cache')
self.expiration_thread = t
set_daemon(t, True)
t.daemon = True
t.start()
def clear(self):
@ -265,7 +265,7 @@ class MemoryCache(Cache):
self.store.pop(uri, None)
def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
def get(invalid_methods=('POST', 'PUT', 'DELETE'), debug=False, **kwargs):
"""Try to obtain cached output. If fresh enough, raise HTTPError(304).
If POST, PUT, or DELETE:
@ -291,9 +291,9 @@ def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
request = cherrypy.serving.request
response = cherrypy.serving.response
if not hasattr(cherrypy, "_cache"):
if not hasattr(cherrypy, '_cache'):
# Make a process-wide Cache object.
cherrypy._cache = kwargs.pop("cache_class", MemoryCache)()
cherrypy._cache = kwargs.pop('cache_class', MemoryCache)()
# Take all remaining kwargs and set them on the Cache object.
for k, v in kwargs.items():
@ -328,7 +328,7 @@ def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
if directive == 'max-age':
if len(atoms) != 1 or not atoms[0].isdigit():
raise cherrypy.HTTPError(
400, "Invalid Cache-Control header")
400, 'Invalid Cache-Control header')
max_age = int(atoms[0])
break
elif directive == 'no-cache':
@ -359,7 +359,7 @@ def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs):
dict.__setitem__(rh, k, dict.__getitem__(h, k))
# Add the required Age header
response.headers["Age"] = str(age)
response.headers['Age'] = str(age)
try:
# Note that validate_since depends on a Last-Modified header;
@ -457,14 +457,14 @@ def expires(secs=0, force=False, debug=False):
secs = (86400 * secs.days) + secs.seconds
if secs == 0:
if force or ("Pragma" not in headers):
headers["Pragma"] = "no-cache"
if force or ('Pragma' not in headers):
headers['Pragma'] = 'no-cache'
if cherrypy.serving.request.protocol >= (1, 1):
if force or "Cache-Control" not in headers:
headers["Cache-Control"] = "no-cache, must-revalidate"
if force or 'Cache-Control' not in headers:
headers['Cache-Control'] = 'no-cache, must-revalidate'
# Set an explicit Expires date in the past.
expiry = httputil.HTTPDate(1169942400.0)
else:
expiry = httputil.HTTPDate(response.time + secs)
if force or "Expires" not in headers:
headers["Expires"] = expiry
if force or 'Expires' not in headers:
headers['Expires'] = expiry

45
cherrypy/lib/covercp.py

@ -25,11 +25,12 @@ import sys
import cgi
import os
import os.path
localFile = os.path.join(os.path.dirname(__file__), "coverage.cache")
import cherrypy
from cherrypy._cpcompat import quote_plus
import cherrypy
localFile = os.path.join(os.path.dirname(__file__), 'coverage.cache')
the_coverage = None
try:
@ -45,8 +46,8 @@ except ImportError:
import warnings
warnings.warn(
"No code coverage will be performed; "
"coverage.py could not be imported.")
'No code coverage will be performed; '
'coverage.py could not be imported.')
def start():
pass
@ -196,7 +197,7 @@ def _percent(statements, missing):
return 0
def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
def _show_branch(root, base, path, pct=0, showpct=False, exclude='',
coverage=the_coverage):
# Show the directory name and any of our children
@ -207,7 +208,7 @@ def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
if newpath.lower().startswith(base):
relpath = newpath[len(base):]
yield "| " * relpath.count(os.sep)
yield '| ' * relpath.count(os.sep)
yield (
"<a class='directory' "
"href='menu?base=%s&exclude=%s'>%s</a>\n" %
@ -228,7 +229,7 @@ def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
for name in files:
newpath = os.path.join(path, name)
pc_str = ""
pc_str = ''
if showpct:
try:
_, statements, _, missing, _ = coverage.analysis2(newpath)
@ -237,13 +238,13 @@ def _show_branch(root, base, path, pct=0, showpct=False, exclude="",
pass
else:
pc = _percent(statements, missing)
pc_str = ("%3d%% " % pc).replace(' ', '&nbsp;')
pc_str = ('%3d%% ' % pc).replace(' ', '&nbsp;')
if pc < float(pct) or pc == -1:
pc_str = "<span class='fail'>%s</span>" % pc_str
else:
pc_str = "<span class='pass'>%s</span>" % pc_str
yield TEMPLATE_ITEM % ("| " * (relpath.count(os.sep) + 1),
yield TEMPLATE_ITEM % ('| ' * (relpath.count(os.sep) + 1),
pc_str, newpath, name)
@ -263,8 +264,8 @@ def _graft(path, tree):
break
atoms.append(tail)
atoms.append(p)
if p != "/":
atoms.append("/")
if p != '/':
atoms.append('/')
atoms.reverse()
for node in atoms:
@ -298,7 +299,7 @@ class CoverStats(object):
return TEMPLATE_FRAMESET % self.root.lower()
@cherrypy.expose
def menu(self, base="/", pct="50", showpct="",
def menu(self, base='/', pct='50', showpct='',
exclude=r'python\d\.\d|test|tut\d|tutorial'):
# The coverage module uses all-lower-case names.
@ -309,36 +310,36 @@ class CoverStats(object):
# Start by showing links for parent paths
yield "<div id='crumbs'>"
path = ""
path = ''
atoms = base.split(os.sep)
atoms.pop()
for atom in atoms:
path += atom + os.sep
yield ("<a href='menu?base=%s&exclude=%s'>%s</a> %s"
% (path, quote_plus(exclude), atom, os.sep))
yield "</div>"
yield '</div>'
yield "<div id='tree'>"
# Then display the tree
tree = get_tree(base, exclude, self.coverage)
if not tree:
yield "<p>No modules covered.</p>"
yield '<p>No modules covered.</p>'
else:
for chunk in _show_branch(tree, base, "/", pct,
for chunk in _show_branch(tree, base, '/', pct,
showpct == 'checked', exclude,
coverage=self.coverage):
yield chunk
yield "</div>"
yield "</body></html>"
yield '</div>'
yield '</body></html>'
def annotated_file(self, filename, statements, excluded, missing):
source = open(filename, 'r')
buffer = []
for lineno, line in enumerate(source.readlines()):
lineno += 1
line = line.strip("\n\r")
line = line.strip('\n\r')
empty_the_buffer = True
if lineno in excluded:
template = TEMPLATE_LOC_EXCLUDED
@ -374,7 +375,7 @@ class CoverStats(object):
def serve(path=localFile, port=8080, root=None):
if coverage is None:
raise ImportError("The coverage module could not be imported.")
raise ImportError('The coverage module could not be imported.')
from coverage import coverage
cov = coverage(data_file=path)
cov.load()
@ -382,9 +383,9 @@ def serve(path=localFile, port=8080, root=None):
import cherrypy
cherrypy.config.update({'server.socket_port': int(port),
'server.thread_pool': 10,
'environment': "production",
'environment': 'production',
})
cherrypy.quickstart(CoverStats(cov, root))
if __name__ == "__main__":
if __name__ == '__main__':
serve(*tuple(sys.argv[1:]))

27
cherrypy/lib/cpstats.py

@ -187,9 +187,17 @@ To format statistics reports::
"""
import logging
import os
import sys
import threading
import time
import cherrypy
from cherrypy._cpcompat import json
# ------------------------------- Statistics -------------------------------- #
import logging
if not hasattr(logging, 'statistics'):
logging.statistics = {}
@ -210,12 +218,6 @@ def extrapolate_statistics(scope):
# -------------------- CherryPy Applications Statistics --------------------- #
import sys
import threading
import time
import cherrypy
appstats = logging.statistics.setdefault('CherryPy Applications', {})
appstats.update({
'Enabled': True,
@ -390,24 +392,13 @@ class StatsTool(cherrypy.Tool):
sq.pop(0)
import cherrypy
cherrypy.tools.cpstats = StatsTool()
# ---------------------- CherryPy Statistics Reporting ---------------------- #
import os
thisdir = os.path.abspath(os.path.dirname(__file__))
try:
import json
except ImportError:
try:
import simplejson as json
except ImportError:
json = None
missing = object()
locale_date = lambda v: time.strftime('%c', time.gmtime(v))

92
cherrypy/lib/cptools.py

@ -7,7 +7,7 @@ from hashlib import md5
import six
import cherrypy
from cherrypy._cpcompat import basestring
from cherrypy._cpcompat import text_or_bytes
from cherrypy.lib import httputil as _httputil
from cherrypy.lib import is_iterator
@ -33,7 +33,7 @@ def validate_etags(autotags=False, debug=False):
response = cherrypy.serving.response
# Guard against being run twice.
if hasattr(response, "ETag"):
if hasattr(response, 'ETag'):
return
status, reason, msg = _httputil.valid_status(response.status)
@ -72,24 +72,24 @@ def validate_etags(autotags=False, debug=False):
if debug:
cherrypy.log('If-Match conditions: %s' % repr(conditions),
'TOOLS.ETAGS')
if conditions and not (conditions == ["*"] or etag in conditions):
raise cherrypy.HTTPError(412, "If-Match failed: ETag %r did "
"not match %r" % (etag, conditions))
if conditions and not (conditions == ['*'] or etag in conditions):
raise cherrypy.HTTPError(412, 'If-Match failed: ETag %r did '
'not match %r' % (etag, conditions))
conditions = request.headers.elements('If-None-Match') or []
conditions = [str(x) for x in conditions]
if debug:
cherrypy.log('If-None-Match conditions: %s' % repr(conditions),
'TOOLS.ETAGS')
if conditions == ["*"] or etag in conditions:
if conditions == ['*'] or etag in conditions:
if debug:
cherrypy.log('request.method: %s' %
request.method, 'TOOLS.ETAGS')
if request.method in ("GET", "HEAD"):
if request.method in ('GET', 'HEAD'):
raise cherrypy.HTTPRedirect([], 304)
else:
raise cherrypy.HTTPError(412, "If-None-Match failed: ETag %r "
"matched %r" % (etag, conditions))
raise cherrypy.HTTPError(412, 'If-None-Match failed: ETag %r '
'matched %r' % (etag, conditions))
def validate_since():
@ -113,7 +113,7 @@ def validate_since():
since = request.headers.get('If-Modified-Since')
if since and since == lastmod:
if (status >= 200 and status <= 299) or status == 304:
if request.method in ("GET", "HEAD"):
if request.method in ('GET', 'HEAD'):
raise cherrypy.HTTPRedirect([], 304)
else:
raise cherrypy.HTTPError(412)
@ -186,7 +186,7 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
# This is for lighttpd/pound/Mongrel's 'X-Forwarded-Proto: https'
scheme = s
if not scheme:
scheme = request.base[:request.base.find("://")]
scheme = request.base[:request.base.find('://')]
if local:
lbase = request.headers.get(local, None)
@ -200,9 +200,9 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
if port != 80:
base += ':%s' % port
if base.find("://") == -1:
if base.find('://') == -1:
# add http:// or https:// if needed
base = scheme + "://" + base
base = scheme + '://' + base
request.base = base
@ -212,7 +212,7 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
cherrypy.log('Testing remote %r:%r' % (remote, xff), 'TOOLS.PROXY')
if xff:
if remote == 'X-Forwarded-For':
#Bug #1268
# Bug #1268
xff = xff.split(',')[0].strip()
request.remote.ip = xff
@ -285,7 +285,7 @@ class SessionAuth(object):
"""Assert that the user is logged in."""
session_key = "username"
session_key = 'username'
debug = False
def check_username_and_password(self, username, password):
@ -317,7 +317,7 @@ Message: %(error_msg)s
<br />
<input type="submit" />
</form>
</body></html>""") % vars()).encode("utf-8")
</body></html>""") % vars()).encode('utf-8')
def do_login(self, username, password, from_page='..', **kwargs):
"""Login. May raise redirect, or return True if request handled."""
@ -326,15 +326,15 @@ Message: %(error_msg)s
if error_msg:
body = self.login_screen(from_page, username, error_msg)
response.body = body
if "Content-Length" in response.headers:
if 'Content-Length' in response.headers:
# Delete Content-Length header so finalize() recalcs it.
del response.headers["Content-Length"]
del response.headers['Content-Length']
return True
else:
cherrypy.serving.request.login = username
cherrypy.session[self.session_key] = username
self.on_login(username)
raise cherrypy.HTTPRedirect(from_page or "/")
raise cherrypy.HTTPRedirect(from_page or '/')
def do_logout(self, from_page='..', **kwargs):
"""Logout. May raise redirect, or return True if request handled."""
@ -364,9 +364,9 @@ Message: %(error_msg)s
locals(),
)
response.body = self.login_screen(url)
if "Content-Length" in response.headers:
if 'Content-Length' in response.headers:
# Delete Content-Length header so finalize() recalcs it.
del response.headers["Content-Length"]
del response.headers['Content-Length']
return True
self._debug_message('Setting request.login to %(username)r', locals())
request.login = username
@ -388,14 +388,14 @@ Message: %(error_msg)s
return True
elif path.endswith('do_login'):
if request.method != 'POST':
response.headers['Allow'] = "POST"
response.headers['Allow'] = 'POST'
self._debug_message('do_login requires POST')
raise cherrypy.HTTPError(405)
self._debug_message('routing %(path)r to do_login', locals())
return self.do_login(**request.params)
elif path.endswith('do_logout'):
if request.method != 'POST':
response.headers['Allow'] = "POST"
response.headers['Allow'] = 'POST'
raise cherrypy.HTTPError(405)
self._debug_message('routing %(path)r to do_logout', locals())
return self.do_logout(**request.params)
@ -414,19 +414,19 @@ session_auth.__doc__ = """Session authentication hook.
Any attribute of the SessionAuth class may be overridden via a keyword arg
to this function:
""" + "\n".join(["%s: %s" % (k, type(getattr(SessionAuth, k)).__name__)
for k in dir(SessionAuth) if not k.startswith("__")])
""" + '\n'.join(['%s: %s' % (k, type(getattr(SessionAuth, k)).__name__)
for k in dir(SessionAuth) if not k.startswith('__')])
def log_traceback(severity=logging.ERROR, debug=False):
"""Write the last error's traceback to the cherrypy error log."""
cherrypy.log("", "HTTP", severity=severity, traceback=True)
cherrypy.log('', 'HTTP', severity=severity, traceback=True)
def log_request_headers(debug=False):
"""Write request headers to the cherrypy error log."""
h = [" %s: %s" % (k, v) for k, v in cherrypy.serving.request.header_list]
cherrypy.log('\nRequest Headers:\n' + '\n'.join(h), "HTTP")
h = [' %s: %s' % (k, v) for k, v in cherrypy.serving.request.header_list]
cherrypy.log('\nRequest Headers:\n' + '\n'.join(h), 'HTTP')
def log_hooks(debug=False):
@ -442,13 +442,13 @@ def log_hooks(debug=False):
points.append(k)
for k in points:
msg.append(" %s:" % k)
msg.append(' %s:' % k)
v = request.hooks.get(k, [])
v.sort()
for h in v:
msg.append(" %r" % h)
msg.append(' %r' % h)
cherrypy.log('\nRequest Hooks for ' + cherrypy.url() +
':\n' + '\n'.join(msg), "HTTP")
':\n' + '\n'.join(msg), 'HTTP')
def redirect(url='', internal=True, debug=False):
@ -533,7 +533,7 @@ def accept(media=None, debug=False):
"""
if not media:
return
if isinstance(media, basestring):
if isinstance(media, text_or_bytes):
media = [media]
request = cherrypy.serving.request
@ -549,12 +549,12 @@ def accept(media=None, debug=False):
# Note that 'ranges' is sorted in order of preference
for element in ranges:
if element.qvalue > 0:
if element.value == "*/*":
if element.value == '*/*':
# Matches any type or subtype
if debug:
cherrypy.log('Match due to */*', 'TOOLS.ACCEPT')
return media[0]
elif element.value.endswith("/*"):
elif element.value.endswith('/*'):
# Matches any subtype
mtype = element.value[:-1] # Keep the slash
for m in media:
@ -574,11 +574,11 @@ def accept(media=None, debug=False):
# No suitable media-range found.
ah = request.headers.get('Accept')
if ah is None:
msg = "Your client did not send an Accept header."
msg = 'Your client did not send an Accept header.'
else:
msg = "Your client sent this Accept header: %s." % ah
msg += (" But this resource only emits these media types: %s." %
", ".join(media))
msg = 'Your client sent this Accept header: %s.' % ah
msg += (' But this resource only emits these media types: %s.' %
', '.join(media))
raise cherrypy.HTTPError(406, msg)
@ -630,3 +630,19 @@ def autovary(ignore=None, debug=False):
v.sort()
resp_h['Vary'] = ', '.join(v)
request.hooks.attach('before_finalize', set_response_header, 95)
def convert_params(exception=ValueError, error=400):
"""Convert request params based on function annotations, with error handling.
exception
Exception class to catch.
status
The HTTP error code to return to the client on failure.
"""
request = cherrypy.serving.request
types = request.handler.callable.__annotations__
with cherrypy.HTTPError.handle(exception, error):
for key in set(types).intersection(request.params):
request.params[key] = types[key](request.params[key])

48
cherrypy/lib/encoding.py

@ -5,7 +5,7 @@ import io
import six
import cherrypy
from cherrypy._cpcompat import basestring, ntob
from cherrypy._cpcompat import text_or_bytes, ntob
from cherrypy.lib import file_generator
from cherrypy.lib import is_closable_iterator
from cherrypy.lib import set_vary_header
@ -66,7 +66,7 @@ class UTF8StreamEncoder:
class ResponseEncoder:
default_encoding = 'utf-8'
failmsg = "Response body could not be encoded with %r."
failmsg = 'Response body could not be encoded with %r.'
encoding = None
errors = 'strict'
text_only = True
@ -131,7 +131,7 @@ class ResponseEncoder:
encoder = self.encode_stream
else:
encoder = self.encode_string
if "Content-Length" in response.headers:
if 'Content-Length' in response.headers:
# Delete Content-Length header so finalize() recalcs it.
# Encoded strings may be of different lengths from their
# unicode equivalents, and even from each other. For example:
@ -142,7 +142,7 @@ class ResponseEncoder:
# 6
# >>> len(t.encode("utf7"))
# 8
del response.headers["Content-Length"]
del response.headers['Content-Length']
# Parse the Accept-Charset request header, and try to provide one
# of the requested charsets (in order of user preference).
@ -157,7 +157,7 @@ class ResponseEncoder:
if self.debug:
cherrypy.log('Specified encoding %r' %
encoding, 'TOOLS.ENCODE')
if (not charsets) or "*" in charsets or encoding in charsets:
if (not charsets) or '*' in charsets or encoding in charsets:
if self.debug:
cherrypy.log('Attempting encoding %r' %
encoding, 'TOOLS.ENCODE')
@ -177,7 +177,7 @@ class ResponseEncoder:
else:
for element in encs:
if element.qvalue > 0:
if element.value == "*":
if element.value == '*':
# Matches any charset. Try our default.
if self.debug:
cherrypy.log('Attempting default encoding due '
@ -192,7 +192,7 @@ class ResponseEncoder:
if encoder(encoding):
return encoding
if "*" not in charsets:
if '*' not in charsets:
# If no "*" is present in an Accept-Charset field, then all
# character sets not explicitly mentioned get a quality
# value of 0, except for ISO-8859-1, which gets a quality
@ -208,18 +208,18 @@ class ResponseEncoder:
# No suitable encoding found.
ac = request.headers.get('Accept-Charset')
if ac is None:
msg = "Your client did not send an Accept-Charset header."
msg = 'Your client did not send an Accept-Charset header.'
else:
msg = "Your client sent this Accept-Charset header: %s." % ac
_charsets = ", ".join(sorted(self.attempted_charsets))
msg += " We tried these charsets: %s." % (_charsets,)
msg = 'Your client sent this Accept-Charset header: %s.' % ac
_charsets = ', '.join(sorted(self.attempted_charsets))
msg += ' We tried these charsets: %s.' % (_charsets,)
raise cherrypy.HTTPError(406, msg)
def __call__(self, *args, **kwargs):
response = cherrypy.serving.response
self.body = self.oldhandler(*args, **kwargs)
if isinstance(self.body, basestring):
if isinstance(self.body, text_or_bytes):
# strings get wrapped in a list because iterating over a single
# item list is much faster than iterating over every character
# in a long string.
@ -233,14 +233,14 @@ class ResponseEncoder:
elif self.body is None:
self.body = []
ct = response.headers.elements("Content-Type")
ct = response.headers.elements('Content-Type')
if self.debug:
cherrypy.log('Content-Type: %r' % [str(h)
for h in ct], 'TOOLS.ENCODE')
if ct and self.add_charset:
ct = ct[0]
if self.text_only:
if ct.value.lower().startswith("text/"):
if ct.value.lower().startswith('text/'):
if self.debug:
cherrypy.log(
'Content-Type %s starts with "text/"' % ct,
@ -264,7 +264,7 @@ class ResponseEncoder:
if self.debug:
cherrypy.log('Setting Content-Type %s' % ct,
'TOOLS.ENCODE')
response.headers["Content-Type"] = str(ct)
response.headers['Content-Type'] = str(ct)
return self.body
@ -280,11 +280,11 @@ def compress(body, compress_level):
yield ntob('\x08') # CM: compression method
yield ntob('\x00') # FLG: none set
# MTIME: 4 bytes
yield struct.pack("<L", int(time.time()) & int('FFFFFFFF', 16))
yield struct.pack('<L', int(time.time()) & int('FFFFFFFF', 16))
yield ntob('\x02') # XFL: max compression, slowest algo
yield ntob('\xff') # OS: unknown
crc = zlib.crc32(ntob(""))
crc = zlib.crc32(ntob(''))
size = 0
zobj = zlib.compressobj(compress_level,
zlib.DEFLATED, -zlib.MAX_WBITS,
@ -296,9 +296,9 @@ def compress(body, compress_level):
yield zobj.flush()
# CRC32: 4 bytes
yield struct.pack("<L", crc & int('FFFFFFFF', 16))
yield struct.pack('<L', crc & int('FFFFFFFF', 16))
# ISIZE: 4 bytes
yield struct.pack("<L", size & int('FFFFFFFF', 16))
yield struct.pack('<L', size & int('FFFFFFFF', 16))
def decompress(body):
@ -335,7 +335,7 @@ def gzip(compress_level=5, mime_types=['text/html', 'text/plain'],
request = cherrypy.serving.request
response = cherrypy.serving.response
set_vary_header(response, "Accept-Encoding")
set_vary_header(response, 'Accept-Encoding')
if not response.body:
# Response body is empty (might be a 304 for instance)
@ -345,7 +345,7 @@ def gzip(compress_level=5, mime_types=['text/html', 'text/plain'],
# If returning cached content (which should already have been gzipped),
# don't re-zip.
if getattr(request, "cached", False):
if getattr(request, 'cached', False):
if debug:
cherrypy.log('Not gzipping cached response', context='TOOLS.GZIP')
return
@ -413,12 +413,12 @@ def gzip(compress_level=5, mime_types=['text/html', 'text/plain'],
# Return a generator that compresses the page
response.headers['Content-Encoding'] = 'gzip'
response.body = compress(response.body, compress_level)
if "Content-Length" in response.headers:
if 'Content-Length' in response.headers:
# Delete Content-Length header so finalize() recalcs it.
del response.headers["Content-Length"]
del response.headers['Content-Length']
return
if debug:
cherrypy.log('No acceptable encoding found.', context='GZIP')
cherrypy.HTTPError(406, "identity, gzip").set_response()
cherrypy.HTTPError(406, 'identity, gzip').set_response()

57
cherrypy/lib/gctools.py

@ -1,6 +1,5 @@
import gc
import inspect
import os
import sys
import time
@ -36,7 +35,7 @@ class ReferrerTree(object):
refs = gc.get_referrers(obj)
self.ignore.append(refs)
if len(refs) > self.maxparents:
return [("[%s referrers]" % len(refs), [])]
return [('[%s referrers]' % len(refs), [])]
try:
ascendcode = self.ascend.__code__
@ -72,20 +71,20 @@ class ReferrerTree(object):
return self.peek(repr(obj))
if isinstance(obj, dict):
return "{" + ", ".join(["%s: %s" % (self._format(k, descend=False),
return '{' + ', '.join(['%s: %s' % (self._format(k, descend=False),
self._format(v, descend=False))
for k, v in obj.items()]) + "}"
for k, v in obj.items()]) + '}'
elif isinstance(obj, list):
return "[" + ", ".join([self._format(item, descend=False)
for item in obj]) + "]"
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]) + ")"
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)
return '%s: %s' % (type(obj), r)
def format(self, tree):
"""Return a list of string reprs from a nested list of referrers."""
@ -93,7 +92,7 @@ class ReferrerTree(object):
def ascend(branch, depth=1):
for parent, grandparents in branch:
output.append((" " * depth) + self._format(parent))
output.append((' ' * depth) + self._format(parent))
if grandparents:
ascend(grandparents, depth + 1)
ascend(tree)
@ -120,14 +119,14 @@ request_counter.subscribe()
def get_context(obj):
if isinstance(obj, _cprequest.Request):
return "path=%s;stage=%s" % (obj.path_info, obj.stage)
return 'path=%s;stage=%s' % (obj.path_info, obj.stage)
elif isinstance(obj, _cprequest.Response):
return "status=%s" % obj.status
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 ""
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):
@ -136,27 +135,27 @@ class GCRoot(object):
classes = [
(_cprequest.Request, 2, 2,
"Should be 1 in this request thread and 1 in the main thread."),
'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."),
'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."),
'Should be 1 in this request thread only.'),
]
@cherrypy.expose
def index(self):
return "Hello, world!"
return 'Hello, world!'
@cherrypy.expose
def stats(self):
output = ["Statistics:"]
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.")
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
@ -174,11 +173,11 @@ class GCRoot(object):
for x in gc.garbage:
trash[type(x)] = trash.get(type(x), 0) + 1
if trash:
output.insert(0, "\n%s unreachable objects:" % unreachable)
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))
output.append(' ' + repr(pair))
# Check declared classes to verify uncollected instances.
# These don't have to be part of a cycle; they can be
@ -194,24 +193,24 @@ class GCRoot(object):
if lenobj < minobj or lenobj > maxobj:
if minobj == maxobj:
output.append(
"\nExpected %s %r references, got %s." %
'\nExpected %s %r references, got %s.' %
(minobj, cls, lenobj))
else:
output.append(
"\nExpected %s to %s %r references, got %s." %
'\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))
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):" %
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)
return '\n'.join(output)

6
cherrypy/lib/http.py

@ -1,6 +0,0 @@
import warnings
warnings.warn('cherrypy.lib.http has been deprecated and will be removed '
'in CherryPy 3.3 use cherrypy.lib.httputil instead.',
DeprecationWarning)
from cherrypy.lib.httputil import *

129
cherrypy/lib/httpauth.py

@ -20,8 +20,18 @@ Usage:
SUPPORTED_ALGORITHM - list of supported 'Digest' algorithms
SUPPORTED_QOP - list of supported 'Digest' 'qop'.
"""
import time
from hashlib import md5
from cherrypy._cpcompat import (
base64_decode, ntob,
parse_http_list, parse_keqv_list
)
__version__ = 1, 0, 1
__author__ = "Tiago Cogumbreiro <cogumbreiro@users.sf.net>"
__author__ = 'Tiago Cogumbreiro <cogumbreiro@users.sf.net>'
__credits__ = """
Peter van Kampen for its recipe which implement most of Digest
authentication:
@ -56,21 +66,16 @@ 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.
"""
__all__ = ("digestAuth", "basicAuth", "doAuth", "checkResponse",
"parseAuthorization", "SUPPORTED_ALGORITHM", "md5SessionKey",
"calculateNonce", "SUPPORTED_QOP")
__all__ = ('digestAuth', 'basicAuth', 'doAuth', 'checkResponse',
'parseAuthorization', 'SUPPORTED_ALGORITHM', 'md5SessionKey',
'calculateNonce', 'SUPPORTED_QOP')
##########################################################################
import time
from hashlib import md5
from cherrypy._cpcompat import base64_decode, ntob
from cherrypy._cpcompat import parse_http_list, parse_keqv_list
MD5 = "MD5"
MD5_SESS = "MD5-sess"
AUTH = "auth"
AUTH_INT = "auth-int"
MD5 = 'MD5'
MD5_SESS = 'MD5-sess'
AUTH = 'auth'
AUTH_INT = 'auth-int'
SUPPORTED_ALGORITHM = (MD5, MD5_SESS)
SUPPORTED_QOP = (AUTH, AUTH_INT)
@ -95,10 +100,10 @@ def calculateNonce(realm, algorithm=MD5):
try:
encoder = DIGEST_AUTH_ENCODERS[algorithm]
except KeyError:
raise NotImplementedError("The chosen algorithm (%s) does not have "
"an implementation yet" % algorithm)
raise NotImplementedError('The chosen algorithm (%s) does not have '
'an implementation yet' % algorithm)
return encoder("%d:%s" % (time.time(), realm))
return encoder('%d:%s' % (time.time(), realm))
def digestAuth(realm, algorithm=MD5, nonce=None, qop=AUTH):
@ -129,7 +134,7 @@ def doAuth(realm):
This should be set in the HTTP header under the key 'WWW-Authenticate'."""
return digestAuth(realm) + " " + basicAuth(realm)
return digestAuth(realm) + ' ' + basicAuth(realm)
##########################################################################
@ -143,31 +148,31 @@ def _parseDigestAuthorization(auth_params):
# Now validate the params
# Check for required parameters
required = ["username", "realm", "nonce", "uri", "response"]
required = ['username', 'realm', 'nonce', 'uri', 'response']
for k in required:
if k not in params:
return None
# If qop is sent then cnonce and nc MUST be present
if "qop" in params and not ("cnonce" in params
and "nc" in params):
if 'qop' in params and not ('cnonce' in params
and 'nc' in params):
return None
# If qop is not sent, neither cnonce nor nc can be present
if ("cnonce" in params or "nc" in params) and \
"qop" not in params:
if ('cnonce' in params or 'nc' in params) and \
'qop' not in params:
return None
return params
def _parseBasicAuthorization(auth_params):
username, password = base64_decode(auth_params).split(":", 1)
return {"username": username, "password": password}
username, password = base64_decode(auth_params).split(':', 1)
return {'username': username, 'password': password}
AUTH_SCHEMES = {
"basic": _parseBasicAuthorization,
"digest": _parseDigestAuthorization,
'basic': _parseBasicAuthorization,
'digest': _parseDigestAuthorization,
}
@ -178,7 +183,7 @@ def parseAuthorization(credentials):
global AUTH_SCHEMES
auth_scheme, auth_params = credentials.split(" ", 1)
auth_scheme, auth_params = credentials.split(' ', 1)
auth_scheme = auth_scheme.lower()
parser = AUTH_SCHEMES[auth_scheme]
@ -187,8 +192,8 @@ def parseAuthorization(credentials):
if params is None:
return
assert "auth_scheme" not in params
params["auth_scheme"] = auth_scheme
assert 'auth_scheme' not in params
params['auth_scheme'] = auth_scheme
return params
@ -214,50 +219,50 @@ def md5SessionKey(params, password):
specification.
"""
keys = ("username", "realm", "nonce", "cnonce")
keys = ('username', 'realm', 'nonce', 'cnonce')
params_copy = {}
for key in keys:
params_copy[key] = params[key]
params_copy["algorithm"] = MD5_SESS
params_copy['algorithm'] = MD5_SESS
return _A1(params_copy, password)
def _A1(params, password):
algorithm = params.get("algorithm", MD5)
algorithm = params.get('algorithm', MD5)
H = DIGEST_AUTH_ENCODERS[algorithm]
if algorithm == MD5:
# If the "algorithm" directive's value is "MD5" or is
# unspecified, then A1 is:
# A1 = unq(username-value) ":" unq(realm-value) ":" passwd
return "%s:%s:%s" % (params["username"], params["realm"], password)
return '%s:%s:%s' % (params['username'], params['realm'], password)
elif algorithm == MD5_SESS:
# This is A1 if qop is set
# A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
# ":" unq(nonce-value) ":" unq(cnonce-value)
h_a1 = H("%s:%s:%s" % (params["username"], params["realm"], password))
return "%s:%s:%s" % (h_a1, params["nonce"], params["cnonce"])
h_a1 = H('%s:%s:%s' % (params['username'], params['realm'], password))
return '%s:%s:%s' % (h_a1, params['nonce'], params['cnonce'])
def _A2(params, method, kwargs):
# If the "qop" directive's value is "auth" or is unspecified, then A2 is:
# A2 = Method ":" digest-uri-value
qop = params.get("qop", "auth")
if qop == "auth":
return method + ":" + params["uri"]
elif qop == "auth-int":
qop = params.get('qop', 'auth')
if qop == 'auth':
return method + ':' + params['uri']
elif qop == 'auth-int':
# If the "qop" value is "auth-int", then A2 is:
# A2 = Method ":" digest-uri-value ":" H(entity-body)
entity_body = kwargs.get("entity_body", "")
H = kwargs["H"]
entity_body = kwargs.get('entity_body', '')
H = kwargs['H']
return "%s:%s:%s" % (
return '%s:%s:%s' % (
method,
params["uri"],
params['uri'],
H(entity_body)
)
@ -265,19 +270,19 @@ def _A2(params, method, kwargs):
raise NotImplementedError("The 'qop' method is unknown: %s" % qop)
def _computeDigestResponse(auth_map, password, method="GET", A1=None,
def _computeDigestResponse(auth_map, password, method='GET', A1=None,
**kwargs):
"""
Generates a response respecting the algorithm defined in RFC 2617
"""
params = auth_map
algorithm = params.get("algorithm", MD5)
algorithm = params.get('algorithm', MD5)
H = DIGEST_AUTH_ENCODERS[algorithm]
KD = lambda secret, data: H(secret + ":" + data)
KD = lambda secret, data: H(secret + ':' + data)
qop = params.get("qop", None)
qop = params.get('qop', None)
H_A2 = H(_A2(params, method, kwargs))
@ -286,7 +291,7 @@ def _computeDigestResponse(auth_map, password, method="GET", A1=None,
else:
H_A1 = H(_A1(params, password))
if qop in ("auth", "auth-int"):
if qop in ('auth', 'auth-int'):
# If the "qop" value is "auth" or "auth-int":
# request-digest = <"> < KD ( H(A1), unq(nonce-value)
# ":" nc-value
@ -294,11 +299,11 @@ def _computeDigestResponse(auth_map, password, method="GET", A1=None,
# ":" unq(qop-value)
# ":" H(A2)
# ) <">
request = "%s:%s:%s:%s:%s" % (
params["nonce"],
params["nc"],
params["cnonce"],
params["qop"],
request = '%s:%s:%s:%s:%s' % (
params['nonce'],
params['nc'],
params['cnonce'],
params['qop'],
H_A2,
)
elif qop is None:
@ -306,12 +311,12 @@ def _computeDigestResponse(auth_map, password, method="GET", A1=None,
# for compatibility with RFC 2069):
# request-digest =
# <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) > <">
request = "%s:%s" % (params["nonce"], H_A2)
request = '%s:%s' % (params['nonce'], H_A2)
return KD(H_A1, request)
def _checkDigestResponse(auth_map, password, method="GET", A1=None, **kwargs):
def _checkDigestResponse(auth_map, password, method='GET', A1=None, **kwargs):
"""This function is used to verify the response given by the client when
he tries to authenticate.
Optional arguments:
@ -329,7 +334,7 @@ def _checkDigestResponse(auth_map, password, method="GET", A1=None, **kwargs):
response = _computeDigestResponse(
auth_map, password, method, A1, **kwargs)
return response == auth_map["response"]
return response == auth_map['response']
def _checkBasicResponse(auth_map, password, method='GET', encrypt=None,
@ -339,19 +344,19 @@ def _checkBasicResponse(auth_map, password, method='GET', encrypt=None,
pass_through = lambda password, username=None: password
encrypt = encrypt or pass_through
try:
candidate = encrypt(auth_map["password"], auth_map["username"])
candidate = encrypt(auth_map['password'], auth_map['username'])
except TypeError:
# if encrypt only takes one parameter, it's the password
candidate = encrypt(auth_map["password"])
candidate = encrypt(auth_map['password'])
return candidate == password
AUTH_RESPONSES = {
"basic": _checkBasicResponse,
"digest": _checkDigestResponse,
'basic': _checkBasicResponse,
'digest': _checkDigestResponse,
}
def checkResponse(auth_map, password, method="GET", encrypt=None, **kwargs):
def checkResponse(auth_map, password, method='GET', encrypt=None, **kwargs):
"""'checkResponse' compares the auth_map with the password and optionally
other arguments that each implementation might need.
@ -368,6 +373,6 @@ def checkResponse(auth_map, password, method="GET", encrypt=None, **kwargs):
The 'A1' argument is only used in MD5_SESS algorithm based responses.
Check md5SessionKey() for more info.
"""
checker = AUTH_RESPONSES[auth_map["auth_scheme"]]
checker = AUTH_RESPONSES[auth_map['auth_scheme']]
return checker(auth_map, password, method=method, encrypt=encrypt,
**kwargs)

104
cherrypy/lib/httputil.py

@ -7,13 +7,23 @@ FuManChu will personally hang you up by your thumbs and submit you
to a public caning.
"""
import functools
import email.utils
import re
from binascii import b2a_base64
from cgi import parse_header
try:
# Python 3
from email.header import decode_header
except ImportError:
from email.Header import decode_header
import six
from cherrypy._cpcompat import BaseHTTPRequestHandler, HTTPDate, ntob, ntou
from cherrypy._cpcompat import basestring, iteritems
from cherrypy._cpcompat import BaseHTTPRequestHandler, ntob, ntou
from cherrypy._cpcompat import text_or_bytes, iteritems
from cherrypy._cpcompat import reversed, sorted, unquote_qs
response_codes = BaseHTTPRequestHandler.responses.copy()
# From https://github.com/cherrypy/cherrypy/issues/361
@ -25,8 +35,8 @@ response_codes[503] = ('Service Unavailable',
'request due to a temporary overloading or '
'maintenance of the server.')
import re
from cgi import parse_header
HTTPDate = functools.partial(email.utils.formatdate, usegmt=True)
def urljoin(*atoms):
@ -35,11 +45,11 @@ def urljoin(*atoms):
This will correctly join a SCRIPT_NAME and PATH_INFO into the
original URL, even if either atom is blank.
"""
url = "/".join([x for x in atoms if x])
while "//" in url:
url = url.replace("//", "/")
url = '/'.join([x for x in atoms if x])
while '//' in url:
url = url.replace('//', '/')
# Special-case the final url of "", and return "/" instead.
return url or "/"
return url or '/'
def urljoin_bytes(*atoms):
@ -48,11 +58,11 @@ def urljoin_bytes(*atoms):
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("/"))
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("/")
return url or ntob('/')
def protocol_from_http(protocol_str):
@ -75,9 +85,9 @@ def get_ranges(headervalue, content_length):
return None
result = []
bytesunit, byteranges = headervalue.split("=", 1)
for brange in byteranges.split(","):
start, stop = [x.strip() for x in brange.split("-", 1)]
bytesunit, byteranges = headervalue.split('=', 1)
for brange in byteranges.split(','):
start, stop = [x.strip() for x in brange.split('-', 1)]
if start:
if not stop:
stop = content_length - 1
@ -135,8 +145,8 @@ class HeaderElement(object):
return self.value < other.value
def __str__(self):
p = [";%s=%s" % (k, v) for k, v in iteritems(self.params)]
return str("%s%s" % (self.value, "".join(p)))
p = [';%s=%s' % (k, v) for k, v in iteritems(self.params)]
return str('%s%s' % (self.value, ''.join(p)))
def __bytes__(self):
return ntob(self.__str__())
@ -144,17 +154,17 @@ class HeaderElement(object):
def __unicode__(self):
return ntou(self.__str__())
@staticmethod
def parse(elementstr):
"""Transform 'token;key=val' to ('token', {'key': 'val'})."""
initial_value, params = parse_header(elementstr)
return initial_value, params
parse = staticmethod(parse)
@classmethod
def from_str(cls, elementstr):
"""Construct an instance from a string of the form 'token;key=val'."""
ival, params = cls.parse(elementstr)
return cls(ival, params)
from_str = classmethod(from_str)
q_separator = re.compile(r'; *q *=')
@ -171,6 +181,7 @@ class AcceptElement(HeaderElement):
have been the other way around, but it's too late to fix now.
"""
@classmethod
def from_str(cls, elementstr):
qvalue = None
# The first "q" parameter (if any) separates the initial
@ -184,16 +195,16 @@ class AcceptElement(HeaderElement):
media_type, params = cls.parse(media_range)
if qvalue is not None:
params["q"] = qvalue
params['q'] = qvalue
return cls(media_type, params)
from_str = classmethod(from_str)
@property
def qvalue(self):
val = self.params.get("q", "1")
'The qvalue, or priority, of this value.'
val = self.params.get('q', '1')
if isinstance(val, HeaderElement):
val = val.value
return float(val)
qvalue = property(qvalue, doc="The qvalue, or priority, of this value.")
def __cmp__(self, other):
diff = cmp(self.qvalue, other.qvalue)
@ -216,7 +227,7 @@ def header_elements(fieldname, fieldvalue):
result = []
for element in RE_HEADER_SPLIT.split(fieldvalue):
if fieldname.startswith("Accept") or fieldname == 'TE':
if fieldname.startswith('Accept') or fieldname == 'TE':
hv = AcceptElement.from_str(element)
else:
hv = HeaderElement.from_str(element)
@ -227,13 +238,8 @@ def header_elements(fieldname, fieldvalue):
def decode_TEXT(value):
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 = ""
decodedvalue = ''
for atom, charset in atoms:
if charset is not None:
atom = atom.decode(charset)
@ -254,7 +260,7 @@ def valid_status(status):
status = 200
status = str(status)
parts = status.split(" ", 1)
parts = status.split(' ', 1)
if len(parts) == 1:
# No reason supplied.
code, = parts
@ -266,16 +272,16 @@ def valid_status(status):
try:
code = int(code)
except ValueError:
raise ValueError("Illegal response status from server "
"(%s is non-numeric)." % repr(code))
raise ValueError('Illegal response status from server '
'(%s is non-numeric).' % repr(code))
if code < 100 or code > 599:
raise ValueError("Illegal response status from server "
"(%s is out of range)." % repr(code))
raise ValueError('Illegal response status from server '
'(%s is out of range).' % repr(code))
if code not in response_codes:
# code is unknown but not illegal
default_reason, message = "", ""
default_reason, message = '', ''
else:
default_reason, message = response_codes[code]
@ -316,7 +322,7 @@ def _parse_qs(qs, keep_blank_values=0, strict_parsing=0, encoding='utf-8'):
nv = name_value.split('=', 1)
if len(nv) != 2:
if strict_parsing:
raise ValueError("bad query field: %r" % (name_value,))
raise ValueError('bad query field: %r' % (name_value,))
# Handle case of a control-name with no equal sign
if keep_blank_values:
nv.append('')
@ -334,7 +340,7 @@ def _parse_qs(qs, keep_blank_values=0, strict_parsing=0, encoding='utf-8'):
return d
image_map_pattern = re.compile(r"[0-9]+,[0-9]+")
image_map_pattern = re.compile(r'[0-9]+,[0-9]+')
def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'):
@ -347,7 +353,7 @@ def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'):
if image_map_pattern.match(query_string):
# Server-side image map. Map the coords to 'x' and 'y'
# (like CGI::Request does).
pm = query_string.split(",")
pm = query_string.split(',')
pm = {'x': int(pm[0]), 'y': int(pm[1])}
else:
pm = _parse_qs(query_string, keep_blank_values, encoding=encoding)
@ -384,12 +390,12 @@ class CaseInsensitiveDict(dict):
for k in E.keys():
self[str(k).title()] = E[k]
@classmethod
def fromkeys(cls, seq, value=None):
newdict = cls()
for k in seq:
newdict[str(k).title()] = value
return newdict
fromkeys = classmethod(fromkeys)
def setdefault(self, key, x=None):
key = str(key).title()
@ -428,7 +434,7 @@ class HeaderMap(CaseInsensitiveDict):
"""
protocol = (1, 1)
encodings = ["ISO-8859-1"]
encodings = ['ISO-8859-1']
# Someday, when http-bis is done, this will probably get dropped
# since few servers, clients, or intermediaries do it. But until then,
@ -451,6 +457,7 @@ class HeaderMap(CaseInsensitiveDict):
"""Transform self into a list of (name, value) tuples."""
return list(self.encode_header_items(self.items()))
@classmethod
def encode_header_items(cls, header_items):
"""
Prepare the sequence of name, value tuples into a form suitable for
@ -460,7 +467,7 @@ class HeaderMap(CaseInsensitiveDict):
if isinstance(k, six.text_type):
k = cls.encode(k)
if not isinstance(v, basestring):
if not isinstance(v, text_or_bytes):
v = str(v)
if isinstance(v, six.text_type):
@ -474,8 +481,8 @@ class HeaderMap(CaseInsensitiveDict):
header_translate_deletechars)
yield (k, v)
encode_header_items = classmethod(encode_header_items)
@classmethod
def encode(cls, v):
"""Return the given header name or value, encoded for HTTP output."""
for enc in cls.encodings:
@ -493,10 +500,9 @@ class HeaderMap(CaseInsensitiveDict):
v = b2a_base64(v.encode('utf-8'))
return (ntob('=?utf-8?b?') + v.strip(ntob('\n')) + ntob('?='))
raise ValueError("Could not encode header part %r using "
"any of the encodings %r." %
raise ValueError('Could not encode header part %r using '
'any of the encodings %r.' %
(v, cls.encodings))
encode = classmethod(encode)
class Host(object):
@ -509,9 +515,9 @@ class Host(object):
"""
ip = "0.0.0.0"
ip = '0.0.0.0'
port = 80
name = "unknown.tld"
name = 'unknown.tld'
def __init__(self, ip, port, name=None):
self.ip = ip
@ -521,4 +527,4 @@ class Host(object):
self.name = name
def __repr__(self):
return "httputil.Host(%r, %r, %r)" % (self.ip, self.port, self.name)
return 'httputil.Host(%r, %r, %r)' % (self.ip, self.port, self.name)

10
cherrypy/lib/jsontools.py

@ -1,17 +1,15 @@
import cherrypy
from cherrypy._cpcompat import basestring, ntou, json_encode, json_decode
from cherrypy._cpcompat import text_or_bytes, ntou, json_encode, json_decode
def json_processor(entity):
"""Read application/json data into request.json."""
if not entity.headers.get(ntou("Content-Length"), ntou("")):
if not entity.headers.get(ntou('Content-Length'), ntou('')):
raise cherrypy.HTTPError(411)
body = entity.fp.read()
try:
with cherrypy.HTTPError.handle(ValueError, 400, 'Invalid JSON document'):
cherrypy.serving.request.json = json_decode(body.decode('utf-8'))
except ValueError:
raise cherrypy.HTTPError(400, 'Invalid JSON document')
def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
@ -41,7 +39,7 @@ def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
package importable; otherwise, ValueError is raised during processing.
"""
request = cherrypy.serving.request
if isinstance(content_type, basestring):
if isinstance(content_type, text_or_bytes):
content_type = [content_type]
if force:

289
cherrypy/lib/lockfile.py

@ -1,147 +1,142 @@
"""
Platform-independent file locking. Inspired by and modeled after zc.lockfile.
"""
import os
try:
import msvcrt
except ImportError:
pass
try:
import fcntl
except ImportError:
pass
class LockError(Exception):
"Could not obtain a lock"
msg = "Unable to lock %r"
def __init__(self, path):
super(LockError, self).__init__(self.msg % path)
class UnlockError(LockError):
"Could not release a lock"
msg = "Unable to unlock %r"
# first, a default, naive locking implementation
class LockFile(object):
"""
A default, naive locking implementation. Always fails if the file
already exists.
"""
def __init__(self, path):
self.path = path
try:
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
except OSError:
raise LockError(self.path)
os.close(fd)
def release(self):
os.remove(self.path)
def remove(self):
pass
class SystemLockFile(object):
"""
An abstract base class for platform-specific locking.
"""
def __init__(self, path):
self.path = path
try:
# Open lockfile for writing without truncation:
self.fp = open(path, 'r+')
except IOError:
# If the file doesn't exist, IOError is raised; Use a+ instead.
# Note that there may be a race here. Multiple processes
# could fail on the r+ open and open the file a+, but only
# one will get the the lock and write a pid.
self.fp = open(path, 'a+')
try:
self._lock_file()
except:
self.fp.seek(1)
self.fp.close()
del self.fp
raise
self.fp.write(" %s\n" % os.getpid())
self.fp.truncate()
self.fp.flush()
def release(self):
if not hasattr(self, 'fp'):
return
self._unlock_file()
self.fp.close()
del self.fp
def remove(self):
"""
Attempt to remove the file
"""
try:
os.remove(self.path)
except:
pass
#@abc.abstract_method
# def _lock_file(self):
# """Attempt to obtain the lock on self.fp. Raise LockError if not
# acquired."""
def _unlock_file(self):
"""Attempt to obtain the lock on self.fp. Raise UnlockError if not
released."""
class WindowsLockFile(SystemLockFile):
def _lock_file(self):
# Lock just the first byte
try:
msvcrt.locking(self.fp.fileno(), msvcrt.LK_NBLCK, 1)
except IOError:
raise LockError(self.fp.name)
def _unlock_file(self):
try:
self.fp.seek(0)
msvcrt.locking(self.fp.fileno(), msvcrt.LK_UNLCK, 1)
except IOError:
raise UnlockError(self.fp.name)
if 'msvcrt' in globals():
LockFile = WindowsLockFile
class UnixLockFile(SystemLockFile):
def _lock_file(self):
flags = fcntl.LOCK_EX | fcntl.LOCK_NB
try:
fcntl.flock(self.fp.fileno(), flags)
except IOError:
raise LockError(self.fp.name)
# no need to implement _unlock_file, it will be unlocked on close()
if 'fcntl' in globals():
LockFile = UnixLockFile
"""
Platform-independent file locking. Inspired by and modeled after zc.lockfile.
"""
import os
try:
import msvcrt
except ImportError:
pass
try:
import fcntl
except ImportError:
pass
class LockError(Exception):
'Could not obtain a lock'
msg = 'Unable to lock %r'
def __init__(self, path):
super(LockError, self).__init__(self.msg % path)
class UnlockError(LockError):
'Could not release a lock'
msg = 'Unable to unlock %r'
# first, a default, naive locking implementation
class LockFile(object):
"""
A default, naive locking implementation. Always fails if the file
already exists.
"""
def __init__(self, path):
self.path = path
try:
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
except OSError:
raise LockError(self.path)
os.close(fd)
def release(self):
os.remove(self.path)
def remove(self):
pass
class SystemLockFile(object):
"""
An abstract base class for platform-specific locking.
"""
def __init__(self, path):
self.path = path
try:
# Open lockfile for writing without truncation:
self.fp = open(path, 'r+')
except IOError:
# If the file doesn't exist, IOError is raised; Use a+ instead.
# Note that there may be a race here. Multiple processes
# could fail on the r+ open and open the file a+, but only
# one will get the the lock and write a pid.
self.fp = open(path, 'a+')
try:
self._lock_file()
except:
self.fp.seek(1)
self.fp.close()
del self.fp
raise
self.fp.write(' %s\n' % os.getpid())
self.fp.truncate()
self.fp.flush()
def release(self):
if not hasattr(self, 'fp'):
return
self._unlock_file()
self.fp.close()
del self.fp
def remove(self):
"""
Attempt to remove the file
"""
try:
os.remove(self.path)
except:
pass
def _unlock_file(self):
"""Attempt to obtain the lock on self.fp. Raise UnlockError if not
released."""
class WindowsLockFile(SystemLockFile):
def _lock_file(self):
# Lock just the first byte
try:
msvcrt.locking(self.fp.fileno(), msvcrt.LK_NBLCK, 1)
except IOError:
raise LockError(self.fp.name)
def _unlock_file(self):
try:
self.fp.seek(0)
msvcrt.locking(self.fp.fileno(), msvcrt.LK_UNLCK, 1)
except IOError:
raise UnlockError(self.fp.name)
if 'msvcrt' in globals():
LockFile = WindowsLockFile
class UnixLockFile(SystemLockFile):
def _lock_file(self):
flags = fcntl.LOCK_EX | fcntl.LOCK_NB
try:
fcntl.flock(self.fp.fileno(), flags)
except IOError:
raise LockError(self.fp.name)
# no need to implement _unlock_file, it will be unlocked on close()
if 'fcntl' in globals():
LockFile = UnixLockFile

6
cherrypy/lib/locking.py

@ -11,7 +11,7 @@ class Timer(object):
A simple timer that will indicate when an expiration time has passed.
"""
def __init__(self, expiration):
"Create a timer that expires at `expiration` (UTC datetime)"
'Create a timer that expires at `expiration` (UTC datetime)'
self.expiration = expiration
@classmethod
@ -26,7 +26,7 @@ class Timer(object):
class LockTimeout(Exception):
"An exception when a lock could not be acquired before a timeout period"
'An exception when a lock could not be acquired before a timeout period'
class LockChecker(object):
@ -43,5 +43,5 @@ class LockChecker(object):
def expired(self):
if self.timer.expired():
raise LockTimeout(
"Timeout acquiring lock for %(session_id)s" % vars(self))
'Timeout acquiring lock for %(session_id)s' % vars(self))
return False

59
cherrypy/lib/profiler.py

@ -34,30 +34,31 @@ module from the command line, it will call ``serve()`` for you.
"""
import io
import os
import os.path
import sys
import warnings
import cherrypy
def new_func_strip_path(func_name):
"""Make profiler output more readable by adding `__init__` modules' parents
"""
filename, line, name = func_name
if filename.endswith("__init__.py"):
return os.path.basename(filename[:-12]) + filename[-12:], line, name
return os.path.basename(filename), line, name
try:
import profile
import pstats
def new_func_strip_path(func_name):
"""Make profiler output more readable by adding `__init__` modules' parents
"""
filename, line, name = func_name
if filename.endswith('__init__.py'):
return os.path.basename(filename[:-12]) + filename[-12:], line, name
return os.path.basename(filename), line, name
pstats.func_strip_path = new_func_strip_path
except ImportError:
profile = None
pstats = None
import os
import os.path
import sys
import warnings
_count = 0
@ -66,7 +67,7 @@ class Profiler(object):
def __init__(self, path=None):
if not path:
path = os.path.join(os.path.dirname(__file__), "profile")
path = os.path.join(os.path.dirname(__file__), 'profile')
self.path = path
if not os.path.exists(path):
os.makedirs(path)
@ -75,7 +76,7 @@ class Profiler(object):
"""Dump profile data into self.path."""
global _count
c = _count = _count + 1
path = os.path.join(self.path, "cp_%04d.prof" % c)
path = os.path.join(self.path, 'cp_%04d.prof' % c)
prof = profile.Profile()
result = prof.runcall(func, *args, **params)
prof.dump_stats(path)
@ -85,7 +86,7 @@ class Profiler(object):
""":rtype: list of available profiles.
"""
return [f for f in os.listdir(self.path)
if f.startswith("cp_") and f.endswith(".prof")]
if f.startswith('cp_') and f.endswith('.prof')]
def stats(self, filename, sortby='cumulative'):
""":rtype stats(index): output of print_stats() for the given profile.
@ -125,8 +126,8 @@ class Profiler(object):
@cherrypy.expose
def menu(self):
yield "<h2>Profiling runs</h2>"
yield "<p>Click on one of the runs below to see profiling data.</p>"
yield '<h2>Profiling runs</h2>'
yield '<p>Click on one of the runs below to see profiling data.</p>'
runs = self.statfiles()
runs.sort()
for i in runs:
@ -135,7 +136,6 @@ class Profiler(object):
@cherrypy.expose
def report(self, filename):
import cherrypy
cherrypy.response.headers['Content-Type'] = 'text/plain'
return self.stats(filename)
@ -149,7 +149,7 @@ class ProfileAggregator(Profiler):
self.profiler = profile.Profile()
def run(self, func, *args, **params):
path = os.path.join(self.path, "cp_%04d.prof" % self.count)
path = os.path.join(self.path, 'cp_%04d.prof' % self.count)
result = self.profiler.runcall(func, *args, **params)
self.profiler.dump_stats(path)
return result
@ -174,11 +174,11 @@ class make_app:
"""
if profile is None or pstats is None:
msg = ("Your installation of Python does not have a profile "
msg = ('Your installation of Python does not have a profile '
"module. If you're on Debian, try "
"`sudo apt-get install python-profiler`. "
"See http://www.cherrypy.org/wiki/ProfilingOnDebian "
"for details.")
'`sudo apt-get install python-profiler`. '
'See http://www.cherrypy.org/wiki/ProfilingOnDebian '
'for details.')
warnings.warn(msg)
self.nextapp = nextapp
@ -199,20 +199,19 @@ class make_app:
def serve(path=None, port=8080):
if profile is None or pstats is None:
msg = ("Your installation of Python does not have a profile module. "
msg = ('Your installation of Python does not have a profile module. '
"If you're on Debian, try "
"`sudo apt-get install python-profiler`. "
"See http://www.cherrypy.org/wiki/ProfilingOnDebian "
"for details.")
'`sudo apt-get install python-profiler`. '
'See http://www.cherrypy.org/wiki/ProfilingOnDebian '
'for details.')
warnings.warn(msg)
import cherrypy
cherrypy.config.update({'server.socket_port': int(port),
'server.thread_pool': 10,
'environment': "production",
'environment': 'production',
})
cherrypy.quickstart(Profiler(path))
if __name__ == "__main__":
if __name__ == '__main__':
serve(*tuple(sys.argv[1:]))

45
cherrypy/lib/reprconf.py

@ -25,14 +25,9 @@ except ImportError:
from ConfigParser import ConfigParser
try:
set
text_or_bytes
except NameError:
from sets import Set as set
try:
basestring
except NameError:
basestring = str
text_or_bytes = str
try:
# Python 3
@ -47,7 +42,7 @@ import sys
def as_dict(config):
"""Return a dict from 'config' whether it is a dict, file, or filename."""
if isinstance(config, basestring):
if isinstance(config, text_or_bytes):
config = Parser().dict_from_file(config)
elif hasattr(config, 'read'):
config = Parser().dict_from_file(config)
@ -83,8 +78,8 @@ class NamespaceSet(dict):
# Separate the given config into namespaces
ns_confs = {}
for k in config:
if "." in k:
ns, name = k.split(".", 1)
if '.' in k:
ns, name = k.split('.', 1)
bucket = ns_confs.setdefault(ns, {})
bucket[name] = config[k]
@ -95,7 +90,7 @@ class NamespaceSet(dict):
# for k, v in ns_confs.get(ns, {}).iteritems():
# callable(k, v)
for ns, handler in self.items():
exit = getattr(handler, "__exit__", None)
exit = getattr(handler, '__exit__', None)
if exit:
callable = handler.__enter__()
no_exc = True
@ -120,7 +115,7 @@ class NamespaceSet(dict):
handler(k, v)
def __repr__(self):
return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
return '%s.%s(%s)' % (self.__module__, self.__class__.__name__,
dict.__repr__(self))
def __copy__(self):
@ -155,7 +150,7 @@ class Config(dict):
def update(self, config):
"""Update self from a dict, file or filename."""
if isinstance(config, basestring):
if isinstance(config, text_or_bytes):
# Filename
config = Parser().dict_from_file(config)
elif hasattr(config, 'read'):
@ -192,7 +187,7 @@ class Parser(ConfigParser):
return optionstr
def read(self, filenames):
if isinstance(filenames, basestring):
if isinstance(filenames, text_or_bytes):
filenames = [filenames]
for filename in filenames:
# try:
@ -218,8 +213,8 @@ class Parser(ConfigParser):
value = unrepr(value)
except Exception:
x = sys.exc_info()[1]
msg = ("Config error in section: %r, option: %r, "
"value: %r. Config values must be valid Python." %
msg = ('Config error in section: %r, option: %r, '
'value: %r. Config values must be valid Python.' %
(section, option, value))
raise ValueError(msg, x.__class__.__name__, x.args)
result[section][option] = value
@ -241,7 +236,7 @@ class _Builder2:
def build(self, o):
m = getattr(self, 'build_' + o.__class__.__name__, None)
if m is None:
raise TypeError("unrepr does not recognize %s" %
raise TypeError('unrepr does not recognize %s' %
repr(o.__class__.__name__))
return m(o)
@ -254,7 +249,7 @@ class _Builder2:
# e.g. IronPython 1.0.
return eval(s)
p = compiler.parse("__tempvalue__ = " + s)
p = compiler.parse('__tempvalue__ = ' + s)
return p.getChildren()[1].getChildren()[0].getChildren()[1]
def build_Subscript(self, o):
@ -327,7 +322,7 @@ class _Builder2:
except AttributeError:
pass
raise TypeError("unrepr could not resolve the name %s" % repr(name))
raise TypeError('unrepr could not resolve the name %s' % repr(name))
def build_Add(self, o):
left, right = map(self.build, o.getChildren())
@ -356,7 +351,7 @@ 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" %
raise TypeError('unrepr does not recognize %s' %
repr(o.__class__.__name__))
return m(o)
@ -369,7 +364,7 @@ class _Builder3:
# e.g. IronPython 1.0.
return eval(s)
p = ast.parse("__tempvalue__ = " + s)
p = ast.parse('__tempvalue__ = ' + s)
return p.body[0].value
def build_Subscript(self, o):
@ -397,8 +392,8 @@ class _Builder3:
if kw.arg is None: # double asterix `**`
rst = self.build(kw.value)
if not isinstance(rst, dict):
raise TypeError("Invalid argument for call."
"Must be a mapping object.")
raise TypeError('Invalid argument for call.'
'Must be a mapping object.')
# give preference to the keys set directly from arg=value
for k, v in rst.items():
if k not in kwargs:
@ -471,7 +466,7 @@ class _Builder3:
except AttributeError:
pass
raise TypeError("unrepr could not resolve the name %s" % repr(name))
raise TypeError('unrepr could not resolve the name %s' % repr(name))
def build_NameConstant(self, o):
return o.value
@ -523,7 +518,7 @@ def attributes(full_attribute_name):
"""Load a module and retrieve an attribute of that module."""
# Parse out the path, module, and attribute
last_dot = full_attribute_name.rfind(".")
last_dot = full_attribute_name.rfind('.')
attr_name = full_attribute_name[last_dot + 1:]
mod_path = full_attribute_name[:last_dot]

103
cherrypy/lib/sessions.py

@ -4,13 +4,13 @@ You need to edit your config file to use sessions. Here's an example::
[/]
tools.sessions.on = True
tools.sessions.storage_type = "file"
tools.sessions.storage_class = cherrypy.lib.sessions.FileSession
tools.sessions.storage_path = "/home/site/sessions"
tools.sessions.timeout = 60
This sets the session to be stored in files in the directory
/home/site/sessions, and the session timeout to 60 minutes. If you omit
``storage_type`` the sessions will be saved in RAM.
``storage_class``, the sessions will be saved in RAM.
``tools.sessions.on`` is the only required line for working sessions,
the rest are optional.
@ -95,8 +95,6 @@ import os
import time
import threading
import six
import cherrypy
from cherrypy._cpcompat import copyitems, pickle, random20
from cherrypy.lib import httputil
@ -123,10 +121,10 @@ class Session(object):
self._id = value
for o in self.id_observers:
o(value)
id = property(_get_id, _set_id, doc="The current session ID.")
id = property(_get_id, _set_id, doc='The current session ID.')
timeout = 60
"Number of minutes after which to delete session data."
'Number of minutes after which to delete session data.'
locked = False
"""
@ -139,16 +137,16 @@ class Session(object):
automatically on the first attempt to access session data."""
clean_thread = None
"Class-level Monitor which calls self.clean_up."
'Class-level Monitor which calls self.clean_up.'
clean_freq = 5
"The poll rate for expired session cleanup in minutes."
'The poll rate for expired session cleanup in minutes.'
originalid = None
"The session id passed by the client. May be missing or unsafe."
'The session id passed by the client. May be missing or unsafe.'
missing = False
"True if the session requested by the client did not exist."
'True if the session requested by the client did not exist.'
regenerated = False
"""
@ -156,7 +154,7 @@ class Session(object):
internal calls to regenerate the session id."""
debug = False
"If True, log debug information."
'If True, log debug information.'
# --------------------- Session management methods --------------------- #
@ -472,9 +470,10 @@ class FileSession(Session):
if isinstance(self.lock_timeout, (int, float)):
self.lock_timeout = datetime.timedelta(seconds=self.lock_timeout)
if not isinstance(self.lock_timeout, (datetime.timedelta, type(None))):
raise ValueError("Lock timeout must be numeric seconds or "
"a timedelta instance.")
raise ValueError('Lock timeout must be numeric seconds or '
'a timedelta instance.')
@classmethod
def setup(cls, **kwargs):
"""Set up the storage system for file-based sessions.
@ -486,12 +485,11 @@ class FileSession(Session):
for k, v in kwargs.items():
setattr(cls, k, v)
setup = classmethod(setup)
def _get_file_path(self):
f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
if not os.path.abspath(f).startswith(self.storage_path):
raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
raise cherrypy.HTTPError(400, 'Invalid session id in cookie.')
return f
def _exists(self):
@ -499,12 +497,12 @@ class FileSession(Session):
return os.path.exists(path)
def _load(self, path=None):
assert self.locked, ("The session load without being locked. "
assert self.locked, ('The session load without being locked. '
"Check your tools' priority levels.")
if path is None:
path = self._get_file_path()
try:
f = open(path, "rb")
f = open(path, 'rb')
try:
return pickle.load(f)
finally:
@ -512,21 +510,21 @@ class FileSession(Session):
except (IOError, EOFError):
e = sys.exc_info()[1]
if self.debug:
cherrypy.log("Error loading the session pickle: %s" %
cherrypy.log('Error loading the session pickle: %s' %
e, 'TOOLS.SESSIONS')
return None
def _save(self, expiration_time):
assert self.locked, ("The session was saved without being locked. "
assert self.locked, ('The session was saved without being locked. '
"Check your tools' priority levels.")
f = open(self._get_file_path(), "wb")
f = open(self._get_file_path(), 'wb')
try:
pickle.dump((self._data, expiration_time), f, self.pickle_protocol)
finally:
f.close()
def _delete(self):
assert self.locked, ("The session deletion without being locked. "
assert self.locked, ('The session deletion without being locked. '
"Check your tools' priority levels.")
try:
os.unlink(self._get_file_path())
@ -603,6 +601,7 @@ class MemcachedSession(Session):
servers = ['127.0.0.1:11211']
@classmethod
def setup(cls, **kwargs):
"""Set up the storage system for memcached-based sessions.
@ -614,21 +613,6 @@ class MemcachedSession(Session):
import memcache
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, six.text_type):
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()
@ -651,7 +635,7 @@ class MemcachedSession(Session):
try:
if not self.cache.set(self.id, (self._data, expiration_time), td):
raise AssertionError(
"Session data for id %r not set." % self.id)
'Session data for id %r not set.' % self.id)
finally:
self.mc_lock.release()
@ -680,13 +664,13 @@ class MemcachedSession(Session):
def save():
"""Save any changed session data."""
if not hasattr(cherrypy.serving, "session"):
if not hasattr(cherrypy.serving, 'session'):
return
request = cherrypy.serving.request
response = cherrypy.serving.response
# Guard against running twice
if hasattr(request, "_sessionsaved"):
if hasattr(request, '_sessionsaved'):
return
request._sessionsaved = True
@ -705,8 +689,8 @@ save.failsafe = True
def close():
"""Close the session object for this request."""
sess = getattr(cherrypy.serving, "session", None)
if getattr(sess, "locked", False):
sess = getattr(cherrypy.serving, 'session', None)
if getattr(sess, 'locked', False):
# If the session is still locked we release the lock
sess.release_lock()
if sess.debug:
@ -715,12 +699,19 @@ close.failsafe = True
close.priority = 90
def init(storage_type='ram', path=None, path_header=None, name='session_id',
def init(storage_type=None, path=None, path_header=None, name='session_id',
timeout=60, domain=None, secure=False, clean_freq=5,
persistent=True, httponly=False, debug=False, **kwargs):
persistent=True, httponly=False, debug=False,
# Py27 compat
# *, storage_class=RamSession,
**kwargs):
"""Initialize session object (using cookies).
storage_class
The Session subclass to use. Defaults to RamSession.
storage_type
(deprecated)
One of 'ram', 'file', memcached'. This will be
used to look up the corresponding class in cherrypy.lib.sessions
globals. For example, 'file' will use the FileSession class.
@ -765,10 +756,13 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
you're using for more information.
"""
# Py27 compat
storage_class = kwargs.pop('storage_class', RamSession)
request = cherrypy.serving.request
# Guard against running twice
if hasattr(request, "_session_init_flag"):
if hasattr(request, '_session_init_flag'):
return
request._session_init_flag = True
@ -780,11 +774,18 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
cherrypy.log('ID obtained from request.cookie: %r' % id,
'TOOLS.SESSIONS')
# Find the storage class and call setup (first time only).
storage_class = storage_type.title() + 'Session'
storage_class = globals()[storage_class]
if not hasattr(cherrypy, "session"):
if hasattr(storage_class, "setup"):
first_time = not hasattr(cherrypy, 'session')
if storage_type:
if first_time:
msg = 'storage_type is deprecated. Supply storage_class instead'
cherrypy.log(msg)
storage_class = storage_type.title() + 'Session'
storage_class = globals()[storage_class]
# call setup first time only
if first_time:
if hasattr(storage_class, 'setup'):
storage_class.setup(**kwargs)
# Create and attach a new Session instance to cherrypy.serving.
@ -801,7 +802,7 @@ def init(storage_type='ram', path=None, path_header=None, name='session_id',
sess.id_observers.append(update_cookie)
# Create cherrypy.session which will proxy to cherrypy.serving.session
if not hasattr(cherrypy, "session"):
if not hasattr(cherrypy, 'session'):
cherrypy.session = cherrypy._ThreadLocalProxy('session')
if persistent:
@ -869,7 +870,7 @@ def set_response_cookie(path=None, path_header=None, name='session_id',
cookie[name]['secure'] = 1
if httponly:
if not cookie[name].isReservedKey('httponly'):
raise ValueError("The httponly cookie token is not supported.")
raise ValueError('The httponly cookie token is not supported.')
cookie[name]['httponly'] = 1

56
cherrypy/lib/static.py

@ -71,7 +71,7 @@ def serve_file(path, content_type=None, disposition=None, name=None,
if content_type is None:
# Set content-type based on filename extension
ext = ""
ext = ''
i = path.rfind('.')
if i != -1:
ext = path[i:].lower()
@ -86,7 +86,7 @@ def serve_file(path, content_type=None, disposition=None, name=None,
if name is None:
name = os.path.basename(path)
cd = '%s; filename="%s"' % (disposition, name)
response.headers["Content-Disposition"] = cd
response.headers['Content-Disposition'] = cd
if debug:
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
@ -144,7 +144,7 @@ def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
cd = disposition
else:
cd = '%s; filename="%s"' % (disposition, name)
response.headers["Content-Disposition"] = cd
response.headers['Content-Disposition'] = cd
if debug:
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
@ -158,12 +158,12 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
# HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
request = cherrypy.serving.request
if request.protocol >= (1, 1):
response.headers["Accept-Ranges"] = "bytes"
response.headers['Accept-Ranges'] = 'bytes'
r = httputil.get_ranges(request.headers.get('Range'), content_length)
if r == []:
response.headers['Content-Range'] = "bytes */%s" % content_length
message = ("Invalid Range (first-byte-pos greater than "
"Content-Length)")
response.headers['Content-Range'] = 'bytes */%s' % content_length
message = ('Invalid Range (first-byte-pos greater than '
'Content-Length)')
if debug:
cherrypy.log(message, 'TOOLS.STATIC')
raise cherrypy.HTTPError(416, message)
@ -179,15 +179,15 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
cherrypy.log(
'Single part; start: %r, stop: %r' % (start, stop),
'TOOLS.STATIC')
response.status = "206 Partial Content"
response.status = '206 Partial Content'
response.headers['Content-Range'] = (
"bytes %s-%s/%s" % (start, stop - 1, content_length))
'bytes %s-%s/%s' % (start, stop - 1, content_length))
response.headers['Content-Length'] = r_len
fileobj.seek(start)
response.body = file_generator_limited(fileobj, r_len)
else:
# Return a multipart/byteranges response.
response.status = "206 Partial Content"
response.status = '206 Partial Content'
try:
# Python 3
from email.generator import _make_boundary as make_boundary
@ -195,15 +195,15 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
# Python 2
from mimetools import choose_boundary as make_boundary
boundary = make_boundary()
ct = "multipart/byteranges; boundary=%s" % boundary
ct = 'multipart/byteranges; boundary=%s' % boundary
response.headers['Content-Type'] = ct
if "Content-Length" in response.headers:
if 'Content-Length' in response.headers:
# Delete Content-Length header so finalize() recalcs it.
del response.headers["Content-Length"]
del response.headers['Content-Length']
def file_ranges():
# Apache compatibility:
yield ntob("\r\n")
yield ntob('\r\n')
for start, stop in r:
if debug:
@ -211,23 +211,23 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
'Multipart; start: %r, stop: %r' % (
start, stop),
'TOOLS.STATIC')
yield ntob("--" + boundary, 'ascii')
yield ntob("\r\nContent-type: %s" % content_type,
yield ntob('--' + boundary, 'ascii')
yield ntob('\r\nContent-type: %s' % content_type,
'ascii')
yield ntob(
"\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (
'\r\nContent-range: bytes %s-%s/%s\r\n\r\n' % (
start, stop - 1, content_length),
'ascii')
fileobj.seek(start)
gen = file_generator_limited(fileobj, stop - start)
for chunk in gen:
yield chunk
yield ntob("\r\n")
yield ntob('\r\n')
# Final boundary
yield ntob("--" + boundary + "--", 'ascii')
yield ntob('--' + boundary + '--', 'ascii')
# Apache compatibility:
yield ntob("\r\n")
yield ntob('\r\n')
response.body = file_ranges()
return response.body
else:
@ -244,7 +244,7 @@ def _serve_fileobj(fileobj, content_type, content_length, debug=False):
def serve_download(path, name=None):
"""Serve 'path' as an application/x-download attachment."""
# This is such a common idiom I felt it deserved its own wrapper.
return serve_file(path, "application/x-download", "attachment", name)
return serve_file(path, 'application/x-download', 'attachment', name)
def _attempt(filename, content_types, debug=False):
@ -268,7 +268,7 @@ def _attempt(filename, content_types, debug=False):
return False
def staticdir(section, dir, root="", match="", content_types=None, index="",
def staticdir(section, dir, root='', match='', content_types=None, index='',
debug=False):
"""Serve a static resource from the given (root +) dir.
@ -306,7 +306,7 @@ def staticdir(section, dir, root="", match="", content_types=None, index="",
# If dir is relative, make absolute using "root".
if not os.path.isabs(dir):
if not root:
msg = "Static dir requires an absolute dir (or root)."
msg = 'Static dir requires an absolute dir (or root).'
if debug:
cherrypy.log(msg, 'TOOLS.STATICDIR')
raise ValueError(msg)
@ -315,10 +315,10 @@ def staticdir(section, dir, root="", match="", content_types=None, index="",
# Determine where we are in the object tree relative to 'section'
# (where the static tool was defined).
if section == 'global':
section = "/"
section = section.rstrip(r"\/")
section = '/'
section = section.rstrip(r'\/')
branch = request.path_info[len(section) + 1:]
branch = unquote(branch.lstrip(r"\/"))
branch = unquote(branch.lstrip(r'\/'))
# If branch is "", filename will end in a slash
filename = os.path.join(dir, branch)
@ -338,11 +338,11 @@ def staticdir(section, dir, root="", match="", content_types=None, index="",
if index:
handled = _attempt(os.path.join(filename, index), content_types)
if handled:
request.is_index = filename[-1] in (r"\/")
request.is_index = filename[-1] in (r'\/')
return handled
def staticfile(filename, root=None, match="", content_types=None, debug=False):
def staticfile(filename, root=None, match='', content_types=None, debug=False):
"""Serve a static resource from the given (root +) filename.
match

4
cherrypy/process/__init__.py

@ -10,5 +10,5 @@ use with the bus. Some use tool-specific channels; see the documentation
for each class.
"""
from cherrypy.process.wspbus import bus
from cherrypy.process import plugins, servers
from cherrypy.process.wspbus import bus # noqa
from cherrypy.process import plugins, servers # noqa

84
cherrypy/process/plugins.py

@ -7,8 +7,8 @@ import sys
import time
import threading
from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident
from cherrypy._cpcompat import ntob, Timer, SetDaemonProperty
from cherrypy._cpcompat import text_or_bytes, get_thread_ident
from cherrypy._cpcompat import ntob, Timer
# _module__file__base is used by Autoreload to make
# absolute any filenames retrieved from sys.modules which are not
@ -104,8 +104,8 @@ class SignalHandler(object):
if sys.platform[:4] == 'java':
del self.handlers['SIGUSR1']
self.handlers['SIGUSR2'] = self.bus.graceful
self.bus.log("SIGUSR1 cannot be set on the JVM platform. "
"Using SIGUSR2 instead.")
self.bus.log('SIGUSR1 cannot be set on the JVM platform. '
'Using SIGUSR2 instead.')
self.handlers['SIGINT'] = self._jython_SIGINT_handler
self._previous_handlers = {}
@ -152,19 +152,19 @@ class SignalHandler(object):
signame = self.signals[signum]
if handler is None:
self.bus.log("Restoring %s handler to SIG_DFL." % signame)
self.bus.log('Restoring %s handler to SIG_DFL.' % signame)
handler = _signal.SIG_DFL
else:
self.bus.log("Restoring %s handler %r." % (signame, handler))
self.bus.log('Restoring %s handler %r.' % (signame, handler))
try:
our_handler = _signal.signal(signum, handler)
if our_handler is None:
self.bus.log("Restored old %s handler %r, but our "
"handler was not registered." %
self.bus.log('Restored old %s handler %r, but our '
'handler was not registered.' %
(signame, handler), level=30)
except ValueError:
self.bus.log("Unable to restore %s handler %r." %
self.bus.log('Unable to restore %s handler %r.' %
(signame, handler), level=40, traceback=True)
def set_handler(self, signal, listener=None):
@ -176,39 +176,39 @@ class SignalHandler(object):
If the given signal name or number is not available on the current
platform, ValueError is raised.
"""
if isinstance(signal, basestring):
if isinstance(signal, text_or_bytes):
signum = getattr(_signal, signal, None)
if signum is None:
raise ValueError("No such signal: %r" % signal)
raise ValueError('No such signal: %r' % signal)
signame = signal
else:
try:
signame = self.signals[signal]
except KeyError:
raise ValueError("No such signal: %r" % signal)
raise ValueError('No such signal: %r' % signal)
signum = signal
prev = _signal.signal(signum, self._handle_signal)
self._previous_handlers[signum] = prev
if listener is not None:
self.bus.log("Listening for %s." % signame)
self.bus.log('Listening for %s.' % signame)
self.bus.subscribe(signame, listener)
def _handle_signal(self, signum=None, frame=None):
"""Python signal handler (self.set_handler subscribes it for you)."""
signame = self.signals[signum]
self.bus.log("Caught signal %s." % signame)
self.bus.log('Caught signal %s.' % signame)
self.bus.publish(signame)
def handle_SIGHUP(self):
"""Restart if daemonized, else exit."""
if self._is_daemonized():
self.bus.log("SIGHUP caught while daemonized. Restarting.")
self.bus.log('SIGHUP caught while daemonized. Restarting.')
self.bus.restart()
else:
# not daemonized (may be foreground or background)
self.bus.log("SIGHUP caught but not daemonized. Exiting.")
self.bus.log('SIGHUP caught but not daemonized. Exiting.')
self.bus.exit()
@ -239,14 +239,14 @@ class DropPrivileges(SimplePlugin):
def _set_uid(self, val):
if val is not None:
if pwd is None:
self.bus.log("pwd module not available; ignoring uid.",
self.bus.log('pwd module not available; ignoring uid.',
level=30)
val = None
elif isinstance(val, basestring):
elif isinstance(val, text_or_bytes):
val = pwd.getpwnam(val)[2]
self._uid = val
uid = property(_get_uid, _set_uid,
doc="The uid under which to run. Availability: Unix.")
doc='The uid under which to run. Availability: Unix.')
def _get_gid(self):
return self._gid
@ -254,14 +254,14 @@ class DropPrivileges(SimplePlugin):
def _set_gid(self, val):
if val is not None:
if grp is None:
self.bus.log("grp module not available; ignoring gid.",
self.bus.log('grp module not available; ignoring gid.',
level=30)
val = None
elif isinstance(val, basestring):
elif isinstance(val, text_or_bytes):
val = grp.getgrnam(val)[2]
self._gid = val
gid = property(_get_gid, _set_gid,
doc="The gid under which to run. Availability: Unix.")
doc='The gid under which to run. Availability: Unix.')
def _get_umask(self):
return self._umask
@ -271,7 +271,7 @@ class DropPrivileges(SimplePlugin):
try:
os.umask
except AttributeError:
self.bus.log("umask function not available; ignoring umask.",
self.bus.log('umask function not available; ignoring umask.',
level=30)
val = None
self._umask = val
@ -393,7 +393,7 @@ class Daemonizer(SimplePlugin):
except OSError:
# Python raises OSError rather than returning negative numbers.
exc = sys.exc_info()[1]
sys.exit("%s: fork #1 failed: (%d) %s\n"
sys.exit('%s: fork #1 failed: (%d) %s\n'
% (sys.argv[0], exc.errno, exc.strerror))
os.setsid()
@ -406,15 +406,15 @@ class Daemonizer(SimplePlugin):
os._exit(0) # Exit second parent
except OSError:
exc = sys.exc_info()[1]
sys.exit("%s: fork #2 failed: (%d) %s\n"
sys.exit('%s: fork #2 failed: (%d) %s\n'
% (sys.argv[0], exc.errno, exc.strerror))
os.chdir("/")
os.chdir('/')
os.umask(0)
si = open(self.stdin, "r")
so = open(self.stdout, "a+")
se = open(self.stderr, "a+")
si = open(self.stdin, 'r')
so = open(self.stdout, 'a+')
se = open(self.stderr, 'a+')
# os.dup2(fd, fd2) will close fd2 if necessary,
# so we don't explicitly close stdin/out/err.
@ -442,7 +442,7 @@ class PIDFile(SimplePlugin):
if self.finalized:
self.bus.log('PID %r already written to %r.' % (pid, self.pidfile))
else:
open(self.pidfile, "wb").write(ntob("%s\n" % pid, 'utf8'))
open(self.pidfile, 'wb').write(ntob('%s\n' % pid, 'utf8'))
self.bus.log('PID %r written to %r.' % (pid, self.pidfile))
self.finalized = True
start.priority = 70
@ -481,13 +481,13 @@ class PerpetualTimer(Timer):
except Exception:
if self.bus:
self.bus.log(
"Error in perpetual timer thread function %r." %
'Error in perpetual timer thread function %r.' %
self.function, level=40, traceback=True)
# Quit on first error to avoid massive logs.
raise
class BackgroundTask(SetDaemonProperty, threading.Thread):
class BackgroundTask(threading.Thread):
"""A subclass of threading.Thread whose run() method repeats.
@ -499,7 +499,7 @@ class BackgroundTask(SetDaemonProperty, threading.Thread):
"""
def __init__(self, interval, function, args=[], kwargs={}, bus=None):
threading.Thread.__init__(self)
super(BackgroundTask, self).__init__()
self.interval = interval
self.function = function
self.args = args
@ -523,7 +523,7 @@ class BackgroundTask(SetDaemonProperty, threading.Thread):
self.function(*self.args, **self.kwargs)
except Exception:
if self.bus:
self.bus.log("Error in background task thread function %r."
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
@ -560,24 +560,24 @@ class Monitor(SimplePlugin):
bus=self.bus)
self.thread.setName(threadname)
self.thread.start()
self.bus.log("Started monitor thread %r." % threadname)
self.bus.log('Started monitor thread %r.' % threadname)
else:
self.bus.log("Monitor thread %r already started." % threadname)
self.bus.log('Monitor thread %r already started.' % threadname)
start.priority = 70
def stop(self):
"""Stop our callback's background task thread."""
if self.thread is None:
self.bus.log("No thread running for %s." %
self.bus.log('No thread running for %s.' %
self.name or self.__class__.__name__)
else:
if self.thread is not threading.currentThread():
name = self.thread.getName()
self.thread.cancel()
if not get_daemon(self.thread):
self.bus.log("Joining %r" % name)
if not self.thread.daemon:
self.bus.log('Joining %r' % name)
self.thread.join()
self.bus.log("Stopped thread %r." % name)
self.bus.log('Stopped thread %r.' % name)
self.thread = None
def graceful(self):
@ -674,10 +674,10 @@ class Autoreloader(Monitor):
else:
if mtime is None or mtime > oldtime:
# The file has been deleted or modified.
self.bus.log("Restarting because %s changed." %
self.bus.log('Restarting because %s changed.' %
filename)
self.thread.cancel()
self.bus.log("Stopped thread %r." %
self.bus.log('Stopped thread %r.' %
self.thread.getName())
self.bus.restart()
return

65
cherrypy/process/servers.py

@ -113,6 +113,7 @@ Please see `Lighttpd FastCGI Docs
an explanation of the possible configuration options.
"""
import os
import sys
import time
import warnings
@ -151,32 +152,33 @@ class ServerAdapter(object):
def start(self):
"""Start the HTTP server."""
if self.bind_addr is None:
on_what = "unknown interface (dynamic?)"
on_what = 'unknown interface (dynamic?)'
elif isinstance(self.bind_addr, tuple):
on_what = self._get_base()
else:
on_what = "socket file: %s" % self.bind_addr
on_what = 'socket file: %s' % self.bind_addr
if self.running:
self.bus.log("Already serving on %s" % on_what)
self.bus.log('Already serving on %s' % on_what)
return
self.interrupt = None
if not self.httpserver:
raise ValueError("No HTTP server has been created.")
raise ValueError('No HTTP server has been created.')
# Start the httpserver in a new thread.
if isinstance(self.bind_addr, tuple):
wait_for_free_port(*self.bind_addr)
if not os.environ.get('LISTEN_PID', None):
# Start the httpserver in a new thread.
if isinstance(self.bind_addr, tuple):
wait_for_free_port(*self.bind_addr)
import threading
t = threading.Thread(target=self._start_http_thread)
t.setName("HTTPServer " + t.getName())
t.setName('HTTPServer ' + t.getName())
t.start()
self.wait()
self.running = True
self.bus.log("Serving on %s" % on_what)
self.bus.log('Serving on %s' % on_what)
start.priority = 75
def _get_base(self):
@ -184,15 +186,15 @@ class ServerAdapter(object):
return ''
host, port = self.bind_addr
if getattr(self.httpserver, 'ssl_adapter', None):
scheme = "https"
scheme = 'https'
if port != 443:
host += ":%s" % port
host += ':%s' % port
else:
scheme = "http"
scheme = 'http'
if port != 80:
host += ":%s" % port
host += ':%s' % port
return "%s://%s" % (scheme, host)
return '%s://%s' % (scheme, host)
def _start_http_thread(self):
"""HTTP servers MUST be running in new threads, so that the
@ -204,32 +206,35 @@ class ServerAdapter(object):
try:
self.httpserver.start()
except KeyboardInterrupt:
self.bus.log("<Ctrl-C> hit: shutting down HTTP server")
self.bus.log('<Ctrl-C> hit: shutting down HTTP server')
self.interrupt = sys.exc_info()[1]
self.bus.exit()
except SystemExit:
self.bus.log("SystemExit raised: shutting down HTTP server")
self.bus.log('SystemExit raised: shutting down HTTP server')
self.interrupt = sys.exc_info()[1]
self.bus.exit()
raise
except:
self.interrupt = sys.exc_info()[1]
self.bus.log("Error in HTTP server: shutting down",
self.bus.log('Error in HTTP server: shutting down',
traceback=True, level=40)
self.bus.exit()
raise
def wait(self):
"""Wait until the HTTP server is ready to receive requests."""
while not getattr(self.httpserver, "ready", False):
while not getattr(self.httpserver, 'ready', False):
if self.interrupt:
raise self.interrupt
time.sleep(.1)
# Wait for port to be occupied
if isinstance(self.bind_addr, tuple):
host, port = self.bind_addr
wait_for_occupied_port(host, port)
if not os.environ.get('LISTEN_PID', None):
# Wait for port to be occupied if not running via socket-activation
# (for socket-activation the port will be managed by systemd )
if isinstance(self.bind_addr, tuple):
host, port = self.bind_addr
wait_for_occupied_port(host, port)
def stop(self):
"""Stop the HTTP server."""
@ -240,9 +245,9 @@ class ServerAdapter(object):
if isinstance(self.bind_addr, tuple):
wait_for_free_port(*self.bind_addr)
self.running = False
self.bus.log("HTTP Server %s shut down" % self.httpserver)
self.bus.log('HTTP Server %s shut down' % self.httpserver)
else:
self.bus.log("HTTP Server %s already shut down" % self.httpserver)
self.bus.log('HTTP Server %s already shut down' % self.httpserver)
stop.priority = 25
def restart(self):
@ -389,10 +394,10 @@ def check_port(host, port, timeout=1.0):
except socket.gaierror:
if ':' in host:
info = [(
socket.AF_INET6, socket.SOCK_STREAM, 0, "", (host, port, 0, 0)
socket.AF_INET6, socket.SOCK_STREAM, 0, '', (host, port, 0, 0)
)]
else:
info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (host, port))]
info = [(socket.AF_INET, socket.SOCK_STREAM, 0, '', (host, port))]
for res in info:
af, socktype, proto, canonname, sa = res
@ -408,8 +413,8 @@ def check_port(host, port, timeout=1.0):
if s:
s.close()
else:
raise IOError("Port %s is in use on %s; perhaps the previous "
"httpserver did not shut down properly." %
raise IOError('Port %s is in use on %s; perhaps the previous '
'httpserver did not shut down properly.' %
(repr(port), repr(host)))
@ -435,7 +440,7 @@ def wait_for_free_port(host, port, timeout=None):
else:
return
raise IOError("Port %r not free on %r" % (port, host))
raise IOError('Port %r not free on %r' % (port, host))
def wait_for_occupied_port(host, port, timeout=None):
@ -455,11 +460,11 @@ def wait_for_occupied_port(host, port, timeout=None):
time.sleep(timeout)
if host == client_host(host):
raise IOError("Port %r not bound on %r" % (port, host))
raise IOError('Port %r not bound on %r' % (port, host))
# On systems where a loopback interface is not available and the
# server is bound to all interfaces, it's difficult to determine
# whether the server is in fact occupying the port. In this case,
# just issue a warning and move on. See issue #1100.
msg = "Unable to verify that the server is bound on %r" % port
msg = 'Unable to verify that the server is bound on %r' % port
warnings.warn(msg)

12
cherrypy/process/win32.py

@ -85,7 +85,7 @@ class Win32Bus(wspbus.Bus):
return self.events[state]
except KeyError:
event = win32event.CreateEvent(None, 0, 0,
"WSPBus %s Event (pid=%r)" %
'WSPBus %s Event (pid=%r)' %
(state.name, os.getpid()))
self.events[state] = event
return event
@ -135,7 +135,7 @@ class _ControlCodes(dict):
for key, val in self.items():
if val is obj:
return key
raise ValueError("The given object could not be found: %r" % obj)
raise ValueError('The given object could not be found: %r' % obj)
control_codes = _ControlCodes({'graceful': 138})
@ -153,14 +153,14 @@ class PyWebService(win32serviceutil.ServiceFramework):
"""Python Web Service."""
_svc_name_ = "Python Web Service"
_svc_display_name_ = "Python Web Service"
_svc_name_ = 'Python Web Service'
_svc_display_name_ = 'Python Web Service'
_svc_deps_ = None # sequence of service names on which this depends
_exe_name_ = "pywebsvc"
_exe_name_ = 'pywebsvc'
_exe_args_ = None # Default to no arguments
# Only exists on Windows 2000 or later, ignored on windows NT
_svc_description_ = "Python Web Service"
_svc_description_ = 'Python Web Service'
def SvcDoRun(self):
from cherrypy import process

110
cherrypy/process/wspbus.py

@ -61,13 +61,20 @@ the new state.::
"""
import atexit
import ctypes
import operator
import os
import subprocess
import sys
import threading
import time
import traceback as _traceback
import warnings
import operator
import six
from cherrypy._cpcompat import _args_from_interpreter_flags
# Here I save the value of os.getcwd(), which, if I am imported early enough,
# will be the directory from which the startup script was run. This is needed
@ -85,9 +92,7 @@ class ChannelFailures(Exception):
delimiter = '\n'
def __init__(self, *args, **kwargs):
# Don't use 'super' here; Exceptions are old-style in Py2.4
# See https://github.com/cherrypy/cherrypy/issues/959
Exception.__init__(self, *args, **kwargs)
super(Exception, self).__init__(*args, **kwargs)
self._exceptions = list()
def handle_exception(self):
@ -117,7 +122,7 @@ class _StateEnum(object):
name = None
def __repr__(self):
return "states.%s" % self.name
return 'states.%s' % self.name
def __setattr__(self, key, value):
if isinstance(value, self.State):
@ -170,9 +175,8 @@ class Bus(object):
def subscribe(self, channel, callback, priority=None):
"""Add the given callback at the given channel (if not present)."""
if channel not in self.listeners:
self.listeners[channel] = set()
self.listeners[channel].add(callback)
ch_listeners = self.listeners.setdefault(channel, set())
ch_listeners.add(callback)
if priority is None:
priority = getattr(callback, 'priority', 50)
@ -215,7 +219,7 @@ class Bus(object):
# Assume any further messages to 'log' will fail.
pass
else:
self.log("Error in %r listener %r" % (channel, listener),
self.log('Error in %r listener %r' % (channel, listener),
level=40, traceback=True)
if exc:
raise exc
@ -225,10 +229,10 @@ class Bus(object):
"""An atexit handler which asserts the Bus is not running."""
if self.state != states.EXITING:
warnings.warn(
"The main thread is exiting, but the Bus is in the %r state; "
"shutting it down automatically now. You must either call "
"bus.block() after start(), or call bus.exit() before the "
"main thread exits." % self.state, RuntimeWarning)
'The main thread is exiting, but the Bus is in the %r state; '
'shutting it down automatically now. You must either call '
'bus.block() after start(), or call bus.exit() before the '
'main thread exits.' % self.state, RuntimeWarning)
self.exit()
def start(self):
@ -244,7 +248,7 @@ class Bus(object):
except (KeyboardInterrupt, SystemExit):
raise
except:
self.log("Shutting down due to error in start listener:",
self.log('Shutting down due to error in start listener:',
level=40, traceback=True)
e_info = sys.exc_info()[1]
try:
@ -321,7 +325,7 @@ class Bus(object):
# It's also good to let them all shut down before allowing
# the main thread to call atexit handlers.
# See https://github.com/cherrypy/cherrypy/issues/751.
self.log("Waiting for child threads to terminate...")
self.log('Waiting for child threads to terminate...')
for t in threading.enumerate():
# Validate the we're not trying to join the MainThread
# that will cause a deadlock and the case exist when
@ -333,13 +337,13 @@ class Bus(object):
not isinstance(t, threading._MainThread)
):
# Note that any dummy (external) threads are always daemonic.
if hasattr(threading.Thread, "daemon"):
if hasattr(threading.Thread, 'daemon'):
# Python 2.6+
d = t.daemon
else:
d = t.isDaemon()
if not d:
self.log("Waiting for thread %s." % t.getName())
self.log('Waiting for thread %s.' % t.getName())
t.join()
if self.execv:
@ -376,14 +380,20 @@ class Bus(object):
This must be called from the main thread, because certain platforms
(OS X) don't allow execv to be called in a child thread very well.
"""
args = sys.argv[:]
try:
args = self._get_true_argv()
except NotImplementedError:
"""It's probably win32"""
args = [sys.executable] + _args_from_interpreter_flags() + sys.argv
self.log('Re-spawning %s' % ' '.join(args))
self._extend_pythonpath(os.environ)
if sys.platform[:4] == 'java':
from _systemrestart import SystemRestart
raise SystemRestart
else:
args.insert(0, sys.executable)
if sys.platform == 'win32':
args = ['"%s"' % arg for arg in args]
@ -392,6 +402,58 @@ class Bus(object):
self._set_cloexec()
os.execv(sys.executable, args)
@staticmethod
def _get_true_argv():
"""Retrieves all real arguments of the python interpreter
...even those not listed in ``sys.argv``
:seealso: http://stackoverflow.com/a/28338254/595220
:seealso: http://stackoverflow.com/a/6683222/595220
:seealso: http://stackoverflow.com/a/28414807/595220
"""
try:
char_p = ctypes.c_char_p if six.PY2 else ctypes.c_wchar_p
argv = ctypes.POINTER(char_p)()
argc = ctypes.c_int()
ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(argc), ctypes.byref(argv))
except AttributeError:
"""It looks Py_GetArgcArgv is completely absent in MS Windows
:seealso: https://github.com/cherrypy/cherrypy/issues/1506
:ref: https://chromium.googlesource.com/infra/infra/+/69eb0279c12bcede5937ce9298020dd4581e38dd%5E!/
"""
raise NotImplementedError
else:
return argv[:argc.value]
@staticmethod
def _extend_pythonpath(env):
"""
If sys.path[0] is an empty string, the interpreter was likely
invoked with -m and the effective path is about to change on
re-exec. Add the current directory to $PYTHONPATH to ensure
that the new process sees the same path.
This issue cannot be addressed in the general case because
Python cannot reliably reconstruct the
original command line (http://bugs.python.org/issue14208).
(This idea filched from tornado.autoreload)
"""
path_prefix = '.' + os.pathsep
existing_path = env.get('PYTHONPATH', '')
needs_patch = (
sys.path[0] == '' and
not existing_path.startswith(path_prefix)
)
if needs_patch:
env['PYTHONPATH'] = path_prefix + existing_path
def _set_cloexec(self):
"""Set the CLOEXEC flag on all open files (except stdin/out/err).
@ -437,16 +499,10 @@ class Bus(object):
return t
def log(self, msg="", level=20, traceback=False):
def log(self, msg='', level=20, traceback=False):
"""Log the given message. Append the last traceback if requested."""
if traceback:
# Work-around for bug in Python's traceback implementation
# which crashes when the error message contains %1, %2 etc.
errors = sys.exc_info()
if '%' in errors[1].message:
errors[1].message = errors[1].message.replace('%', '#')
errors[1].args = [item.replace('%', '#') for item in errors[1].args]
msg += "\n" + "".join(_traceback.format_exception(*errors))
msg += '\n' + ''.join(_traceback.format_exception(*sys.exc_info()))
self.publish('log', msg, level)
bus = Bus()

2571
cherrypy/wsgiserver/__init__.py

File diff suppressed because it is too large

27
cherrypy/wsgiserver/ssl_builtin.py

@ -33,10 +33,10 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
private_key = None
"""The filename of the server's private key file."""
certificate_chain = None
"""The filename of the certificate chain file."""
"""The ssl.SSLContext that will be used to wrap sockets where available
(on Python > 2.7.9 / 3.3)
"""
@ -44,7 +44,7 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
def __init__(self, certificate, private_key, certificate_chain=None):
if ssl is None:
raise ImportError("You must install the ssl module to use HTTPS.")
raise ImportError('You must install the ssl module to use HTTPS.')
self.certificate = certificate
self.private_key = private_key
self.certificate_chain = certificate_chain
@ -79,13 +79,18 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
# the 'ping' isn't SSL.
return None, {}
elif e.errno == ssl.SSL_ERROR_SSL:
if e.args[1].endswith('http request'):
if 'http request' in e.args[1]:
# The client is speaking HTTP to an HTTPS server.
raise wsgiserver.NoSSLError
elif e.args[1].endswith('unknown protocol'):
elif 'unknown protocol' in e.args[1]:
# The client is speaking some non-HTTP protocol.
# Drop the conn.
return None, {}
elif 'handshake operation timed out' in e.args[0]:
# This error is thrown by builtin SSL after a timeout
# when client is speaking HTTP to an HTTPS server.
# The connection can safely be dropped.
return None, {}
raise
return s, self.get_environ(s)
@ -94,8 +99,8 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
"""Create WSGI environ entries to be merged into each request."""
cipher = sock.cipher()
ssl_environ = {
"wsgi.url_scheme": "https",
"HTTPS": "on",
'wsgi.url_scheme': 'https',
'HTTPS': 'on',
'SSL_PROTOCOL': cipher[1],
'SSL_CIPHER': cipher[0]
# SSL_VERSION_INTERFACE string The mod_ssl program version
@ -103,9 +108,5 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
}
return ssl_environ
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)
def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
return wsgiserver.CP_makefile(sock, mode, bufsize)

28
cherrypy/wsgiserver/ssl_pyopenssl.py

@ -43,7 +43,7 @@ except ImportError:
SSL = None
class SSL_fileobject(wsgiserver.CP_fileobject):
class SSL_fileobject(wsgiserver.CP_makefile):
"""SSL file object attached to a socket object."""
@ -70,15 +70,15 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
time.sleep(self.ssl_retry)
except SSL.SysCallError as e:
if is_reader and e.args == (-1, 'Unexpected EOF'):
return ""
return ''
errnum = e.args[0]
if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
return ""
return ''
raise socket.error(errnum)
except SSL.Error as e:
if is_reader and e.args == (-1, 'Unexpected EOF'):
return ""
return ''
thirdarg = None
try:
@ -95,7 +95,7 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
raise
if time.time() - start > self.ssl_timeout:
raise socket.timeout("timed out")
raise socket.timeout('timed out')
def recv(self, size):
return self._safe_call(True, super(SSL_fileobject, self).recv, size)
@ -166,7 +166,7 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
def __init__(self, certificate, private_key, certificate_chain=None):
if SSL is None:
raise ImportError("You must install pyOpenSSL to use HTTPS.")
raise ImportError('You must install pyOpenSSL to use HTTPS.')
self.context = None
self.certificate = certificate
@ -192,18 +192,14 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
c = SSL.Context(SSL.SSLv23_METHOD)
c.use_privatekey_file(self.private_key)
if self.certificate_chain:
if isinstance(self.certificate_chain, unicode) and self.certificate_chain.encode('cp1252', 'ignore') == self.certificate_chain.encode('cp1252', 'replace'):
# Support buggy PyOpenSSL 0.14, which cannot handle Unicode names
c.load_verify_locations(self.certificate_chain.encode('cp1252', 'ignore'))
else:
c.load_verify_locations(self.certificate_chain)
c.load_verify_locations(self.certificate_chain)
c.use_certificate_file(self.certificate)
return c
def get_environ(self):
"""Return WSGI environ entries to be merged into each request."""
ssl_environ = {
"HTTPS": "on",
'HTTPS': 'on',
# pyOpenSSL doesn't provide access to any of these AFAICT
# 'SSL_PROTOCOL': 'SSLv2',
# SSL_CIPHER string The cipher specification name
@ -224,8 +220,8 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
# Validity of server's certificate (end time),
})
for prefix, dn in [("I", cert.get_issuer()),
("S", cert.get_subject())]:
for prefix, dn in [('I', cert.get_issuer()),
('S', cert.get_subject())]:
# X509Name objects don't seem to have a way to get the
# complete DN string. Use str() and slice it instead,
# because str(dn) == "<X509Name object '/C=US/ST=...'>"
@ -237,9 +233,9 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
# The DN should be of the form: /k1=v1/k2=v2, but we must allow
# for any value to contain slashes itself (in a URL).
while dnstr:
pos = dnstr.rfind("=")
pos = dnstr.rfind('=')
dnstr, value = dnstr[:pos], dnstr[pos + 1:]
pos = dnstr.rfind("/")
pos = dnstr.rfind('/')
dnstr, key = dnstr[:pos], dnstr[pos + 1:]
if key and value:
wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)

16
cherrypy/wsgiserver/test_wsgiserver.py

@ -0,0 +1,16 @@
import six
import mock
from cherrypy import wsgiserver
class TestWSGIGateway_u0:
@mock.patch('cherrypy.wsgiserver.WSGIGateway_10.get_environ',
lambda self: {'foo': 'bar'})
def test_decodes_items(self):
req = mock.MagicMock(path=b'/', qs=b'')
gw = wsgiserver.WSGIGateway_u0(req=req)
env = gw.get_environ()
assert env['foo'] == 'bar'
assert isinstance(env['foo'], six.text_type)

2483
cherrypy/wsgiserver/wsgiserver2.py

File diff suppressed because it is too large

2198
cherrypy/wsgiserver/wsgiserver3.py

File diff suppressed because it is too large
Loading…
Cancel
Save