Browse Source

Merge pull request #515 from sabnzbd/feature/cherrypy5

Feature/cherrypy5
pull/548/merge
shypike 9 years ago
parent
commit
7755928e09
  1. 76
      cherrypy/Patch-for-CP-3.8.0.diff
  2. 2
      cherrypy/VERSION.txt
  3. 4
      cherrypy/__init__.py
  4. 0
      cherrypy/__main__.py
  5. 30
      cherrypy/_cpcompat.py
  6. 4
      cherrypy/_cpcompat_subprocess.py
  7. 2
      cherrypy/_cpconfig.py
  8. 1
      cherrypy/_cpdispatch.py
  9. 3
      cherrypy/_cpwsgi.py
  10. 0
      cherrypy/cherryd
  11. 15
      cherrypy/daemon.py
  12. 3
      cherrypy/lib/auth_digest.py
  13. 18
      cherrypy/lib/cpstats.py
  14. 10
      cherrypy/lib/cptools.py
  15. 2
      cherrypy/lib/encoding.py
  16. 4
      cherrypy/lib/httpauth.py
  17. 1
      cherrypy/lib/httputil.py
  18. 2
      cherrypy/lib/profiler.py
  19. 42
      cherrypy/lib/reprconf.py
  20. 5
      cherrypy/lib/static.py
  21. 35
      cherrypy/process/plugins.py
  22. 3
      cherrypy/process/servers.py
  23. 2
      cherrypy/process/wspbus.py
  24. 4
      cherrypy/wsgiserver/ssl_pyopenssl.py
  25. 138
      cherrypy/wsgiserver/wsgiserver2.py
  26. 39
      cherrypy/wsgiserver/wsgiserver3.py

76
cherrypy/Patch-for-CP-3.8.0.diff

@ -1,76 +0,0 @@
From 0f6da83f5acff3fc9c4eda2d3111849ef1429711 Mon Sep 17 00:00:00 2001
From: shypike <shypike@sabnzbd.org>
Date: Thu, 23 Jul 2015 18:16:27 +0200
Subject: [PATCH] Patch CherryPy to support 301 redirection.
Needed to support the broken Bonjour/ZeroConfig protocol that
only allows an HTTP address to set, even for a HTTPS-only server.
---
cherrypy/wsgiserver/wsgiserver2.py | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/cherrypy/wsgiserver/wsgiserver2.py b/cherrypy/wsgiserver/wsgiserver2.py
index c0896d3..9367f7b 100644
--- a/cherrypy/wsgiserver/wsgiserver2.py
+++ b/cherrypy/wsgiserver/wsgiserver2.py
@@ -75,7 +75,7 @@ __all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
'WorkerThread', 'ThreadPool', 'SSLAdapter',
'CherryPyWSGIServer',
'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
- 'WSGIPathInfoDispatcher', 'get_ssl_adapter_class']
+ 'WSGIPathInfoDispatcher', 'get_ssl_adapter_class', 'redirect_url']
import os
try:
@@ -97,6 +97,7 @@ except ImportError:
import StringIO
DEFAULT_BUFFER_SIZE = -1
+REDIRECT_URL = None # Application can write its HTTP-->HTTPS redirection URL here
class FauxSocket(object):
@@ -167,6 +168,12 @@ quoted_slash = re.compile(ntob("(?i)%2F"))
import errno
+def redirect_url(url=None):
+ global REDIRECT_URL
+ if url and '%s' in url:
+ REDIRECT_URL = url
+ return REDIRECT_URL
+
def plat_specific_errors(*errnames):
"""Return error numbers for all errors in errnames on this platform.
@@ -881,6 +888,9 @@ class HTTPRequest(object):
"Content-Length: %s\r\n" % len(msg),
"Content-Type: text/plain\r\n"]
+ if status[:3] in ("301",):
+ buf.append("Location: %s" % msg)
+
if status[:3] in ("413", "414"):
# Request Entity Too Large / Request-URI Too Long
self.close_connection = True
@@ -1394,10 +1404,13 @@ class HTTPConnection(object):
# Unwrap our wfile
self.wfile = CP_fileobject(
self.socket._sock, "wb", self.wbufsize)
- req.simple_response(
- "400 Bad Request",
- "The client sent a plain HTTP request, but "
- "this server only speaks HTTPS on this port.")
+ if REDIRECT_URL:
+ req.simple_response("301 Moved Permanently", REDIRECT_URL % self.remote_addr)
+ else:
+ req.simple_response(
+ "400 Bad Request",
+ "The client sent a plain HTTP request, but "
+ "this server only speaks HTTPS on this port.")
self.linger = True
except Exception:
e = sys.exc_info()[1]
--
1.9.5 (Apple Git-50.3)

2
cherrypy/VERSION.txt

@ -1,4 +1,4 @@
CherryPy 3.8.0 Official distribution: https://pypi.python.org/packages/source/C/CherryPy/CherryPy-3.8.0.tar.gz CherryPy 5.1.0 Official distribution: https://pypi.python.org/packages/source/C/CherryPy/CherryPy-5.1.0.tar.gz
The folders 'tutorial', 'test' and 'scaffold' have been removed. The folders 'tutorial', 'test' and 'scaffold' have been removed.
This file has been added. This file has been added.
A patch is required to enable proper Bonjour/Zeroconfig support. A patch is required to enable proper Bonjour/Zeroconfig support.

4
cherrypy/__init__.py

@ -56,10 +56,10 @@ with customized or extended components. The core API's are:
These API's are described in the `CherryPy specification <https://bitbucket.org/cherrypy/cherrypy/wiki/CherryPySpec>`_. These API's are described in the `CherryPy specification <https://bitbucket.org/cherrypy/cherrypy/wiki/CherryPySpec>`_.
""" """
__version__ = "3.8.0" __version__ = "5.1.0"
from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode from cherrypy._cpcompat import urljoin as _urljoin, urlencode as _urlencode
from cherrypy._cpcompat import basestring, unicodestr, set from cherrypy._cpcompat import basestring, unicodestr
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError from cherrypy._cperror import NotFound, CherryPyException, TimeoutError

0
cherrypy/__main__.py

30
cherrypy/_cpcompat.py

@ -111,11 +111,6 @@ def assert_native(n):
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: try:
set = set
except NameError:
from sets import Set as set
try:
# Python 3.1+ # Python 3.1+
from base64 import decodebytes as _base64_decodebytes from base64 import decodebytes as _base64_decodebytes
except ImportError: except ImportError:
@ -137,17 +132,6 @@ def base64_decode(n, encoding='ISO-8859-1'):
else: else:
return b return b
try:
# Python 2.5+
from hashlib import md5
except ImportError:
from md5 import new as md5
try:
# Python 2.5+
from hashlib import sha1 as sha
except ImportError:
from sha import new as sha
try: try:
sorted = sorted sorted = sorted
@ -333,18 +317,10 @@ except ImportError:
# In Python 3, pickle is the sped-up C version. # In Python 3, pickle is the sped-up C version.
import pickle import pickle
try: import binascii
os.urandom(20)
import binascii
def random20():
return binascii.hexlify(os.urandom(20)).decode('ascii')
except (AttributeError, NotImplementedError):
import random
# os.urandom not available until Python 2.4. Fall back to random.random.
def random20(): def random20():
return sha('%s' % random.random()).hexdigest() return binascii.hexlify(os.urandom(20)).decode('ascii')
try: try:
from _thread import get_ident as get_thread_ident from _thread import get_ident as get_thread_ident

4
cherrypy/_cpcompat_subprocess.py

@ -883,7 +883,7 @@ class Popen(object):
startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = _subprocess.SW_HIDE startupinfo.wShowWindow = _subprocess.SW_HIDE
comspec = os.environ.get("COMSPEC", "cmd.exe") comspec = os.environ.get("COMSPEC", "cmd.exe")
args = '{} /c "{}"'.format(comspec, args) args = '{0} /c "{1}"'.format(comspec, args)
if (_subprocess.GetVersion() >= 0x80000000 or if (_subprocess.GetVersion() >= 0x80000000 or
os.path.basename(comspec).lower() == "command.com"): os.path.basename(comspec).lower() == "command.com"):
# Win9x, or using command.com on NT. We need to # Win9x, or using command.com on NT. We need to
@ -1029,7 +1029,7 @@ class Popen(object):
elif sig == signal.CTRL_BREAK_EVENT: elif sig == signal.CTRL_BREAK_EVENT:
os.kill(self.pid, signal.CTRL_BREAK_EVENT) os.kill(self.pid, signal.CTRL_BREAK_EVENT)
else: else:
raise ValueError("Unsupported signal: {}".format(sig)) raise ValueError("Unsupported signal: {0}".format(sig))
def terminate(self): def terminate(self):
"""Terminates the process """Terminates the process

2
cherrypy/_cpconfig.py

@ -119,7 +119,7 @@ style) context manager.
""" """
import cherrypy import cherrypy
from cherrypy._cpcompat import set, basestring from cherrypy._cpcompat import basestring
from cherrypy.lib import reprconf from cherrypy.lib import reprconf
# Deprecated in CherryPy 3.2--remove in 3.3 # Deprecated in CherryPy 3.2--remove in 3.3

1
cherrypy/_cpdispatch.py

@ -18,7 +18,6 @@ except AttributeError:
classtype = type classtype = type
import cherrypy import cherrypy
from cherrypy._cpcompat import set
class PageHandler(object): class PageHandler(object):

3
cherrypy/_cpwsgi.py

@ -296,7 +296,8 @@ class AppResponse(object):
"""Create a Request object using environ.""" """Create a Request object using environ."""
env = self.environ.get env = self.environ.get
local = httputil.Host('', int(env('SERVER_PORT', 80)), local = httputil.Host('',
int(env('SERVER_PORT', 80) or -1),
env('SERVER_NAME', '')) env('SERVER_NAME', ''))
remote = httputil.Host(env('REMOTE_ADDR', ''), remote = httputil.Host(env('REMOTE_ADDR', ''),
int(env('REMOTE_PORT', -1) or -1), int(env('REMOTE_PORT', -1) or -1),

0
cherrypy/cherryd

15
cherrypy/daemon.py

@ -53,15 +53,12 @@ def start(configfiles=None, daemonize=False, environment=None,
cherrypy.server.unsubscribe() cherrypy.server.unsubscribe()
addr = cherrypy.server.bind_addr addr = cherrypy.server.bind_addr
if fastcgi: cls = (
f = servers.FlupFCGIServer(application=cherrypy.tree, servers.FlupFCGIServer if fastcgi else
bindAddress=addr) servers.FlupSCGIServer if scgi else
elif scgi: servers.FlupCGIServer
f = servers.FlupSCGIServer(application=cherrypy.tree, )
bindAddress=addr) f = cls(application=cherrypy.tree, bindAddress=addr)
else:
f = servers.FlupCGIServer(application=cherrypy.tree,
bindAddress=addr)
s = servers.ServerAdapter(engine, httpserver=f, bind_addr=addr) s = servers.ServerAdapter(engine, httpserver=f, bind_addr=addr)
s.subscribe() s.subscribe()

3
cherrypy/lib/auth_digest.py

@ -23,10 +23,11 @@ __date__ = 'April 2009'
import time import time
from hashlib import md5
from cherrypy._cpcompat import parse_http_list, parse_keqv_list from cherrypy._cpcompat import parse_http_list, parse_keqv_list
import cherrypy import cherrypy
from cherrypy._cpcompat import md5, ntob from cherrypy._cpcompat import ntob
md5_hex = lambda s: md5(ntob(s)).hexdigest() md5_hex = lambda s: md5(ntob(s)).hexdigest()
qop_auth = 'auth' qop_auth = 'auth'

18
cherrypy/lib/cpstats.py

@ -210,6 +210,7 @@ def extrapolate_statistics(scope):
# -------------------- CherryPy Applications Statistics --------------------- # # -------------------- CherryPy Applications Statistics --------------------- #
import sys
import threading import threading
import time import time
@ -294,6 +295,11 @@ class ByteCountWrapper(object):
average_uriset_time = lambda s: s['Count'] and (s['Sum'] / s['Count']) or 0 average_uriset_time = lambda s: s['Count'] and (s['Sum'] / s['Count']) or 0
def _get_threading_ident():
if sys.version_info >= (3, 3):
return threading.get_ident()
return threading._get_ident()
class StatsTool(cherrypy.Tool): class StatsTool(cherrypy.Tool):
"""Record various information about the current request.""" """Record various information about the current request."""
@ -322,7 +328,7 @@ class StatsTool(cherrypy.Tool):
appstats['Current Requests'] += 1 appstats['Current Requests'] += 1
appstats['Total Requests'] += 1 appstats['Total Requests'] += 1
appstats['Requests'][threading._get_ident()] = { appstats['Requests'][_get_threading_ident()] = {
'Bytes Read': None, 'Bytes Read': None,
'Bytes Written': None, 'Bytes Written': None,
# Use a lambda so the ip gets updated by tools.proxy later # Use a lambda so the ip gets updated by tools.proxy later
@ -339,7 +345,7 @@ class StatsTool(cherrypy.Tool):
debug=False, **kwargs): debug=False, **kwargs):
"""Record the end of a request.""" """Record the end of a request."""
resp = cherrypy.serving.response resp = cherrypy.serving.response
w = appstats['Requests'][threading._get_ident()] w = appstats['Requests'][_get_threading_ident()]
r = cherrypy.request.rfile.bytes_read r = cherrypy.request.rfile.bytes_read
w['Bytes Read'] = r w['Bytes Read'] = r
@ -605,7 +611,13 @@ table.stats2 th {
"""Return ([headers], [rows]) for the given collection.""" """Return ([headers], [rows]) for the given collection."""
# E.g., the 'Requests' dict. # E.g., the 'Requests' dict.
headers = [] headers = []
for record in v.itervalues(): try:
# python2
vals = v.itervalues()
except AttributeError:
# python3
vals = v.values()
for record in vals:
for k3 in record: for k3 in record:
format = formatting.get(k3, missing) format = formatting.get(k3, missing)
if format is None: if format is None:

10
cherrypy/lib/cptools.py

@ -2,9 +2,10 @@
import logging import logging
import re import re
from hashlib import md5
import cherrypy import cherrypy
from cherrypy._cpcompat import basestring, md5, set, unicodestr from cherrypy._cpcompat import basestring, unicodestr
from cherrypy.lib import httputil as _httputil from cherrypy.lib import httputil as _httputil
from cherrypy.lib import is_iterator from cherrypy.lib import is_iterator
@ -192,11 +193,10 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
if lbase is not None: if lbase is not None:
base = lbase.split(',')[0] base = lbase.split(',')[0]
if not base: if not base:
base = request.headers.get('Host', '127.0.0.1')
port = request.local.port port = request.local.port
if port == 80: if port != 80:
base = '127.0.0.1' base += ':%s' % port
else:
base = '127.0.0.1:%s' % port
if base.find("://") == -1: if base.find("://") == -1:
# add http:// or https:// if needed # add http:// or https:// if needed

2
cherrypy/lib/encoding.py

@ -2,7 +2,7 @@ import struct
import time import time
import cherrypy import cherrypy
from cherrypy._cpcompat import basestring, BytesIO, ntob, set, unicodestr from cherrypy._cpcompat import basestring, BytesIO, ntob, unicodestr
from cherrypy.lib import file_generator from cherrypy.lib import file_generator
from cherrypy.lib import is_closable_iterator from cherrypy.lib import is_closable_iterator
from cherrypy.lib import set_vary_header from cherrypy.lib import set_vary_header

4
cherrypy/lib/httpauth.py

@ -62,7 +62,9 @@ __all__ = ("digestAuth", "basicAuth", "doAuth", "checkResponse",
########################################################################## ##########################################################################
import time import time
from cherrypy._cpcompat import base64_decode, ntob, md5 from hashlib import md5
from cherrypy._cpcompat import base64_decode, ntob
from cherrypy._cpcompat import parse_http_list, parse_keqv_list from cherrypy._cpcompat import parse_http_list, parse_keqv_list
MD5 = "MD5" MD5 = "MD5"

1
cherrypy/lib/httputil.py

@ -164,6 +164,7 @@ class HeaderElement(object):
atoms = xatoms atoms = xatoms
# End of patch # End of patch
if not atoms: if not atoms:
initial_value = '' initial_value = ''
else: else:

2
cherrypy/lib/profiler.py

@ -8,7 +8,7 @@ You can profile any of your pages as follows::
from cherrypy.lib import profiler from cherrypy.lib import profiler
class Root: class Root:
p = profile.Profiler("/path/to/profile/dir") p = profiler.Profiler("/path/to/profile/dir")
def index(self): def index(self):
self.p.run(self._index) self.p.run(self._index)

42
cherrypy/lib/reprconf.py

@ -281,13 +281,14 @@ class _Builder2:
# Everything else becomes args # Everything else becomes args
else : else :
args.append(self.build(child)) args.append(self.build(child))
return callee(*args, **kwargs) return callee(*args, **kwargs)
def build_Keyword(self, o): def build_Keyword(self, o):
key, value_obj = o.getChildren() key, value_obj = o.getChildren()
value = self.build(value_obj) value = self.build(value_obj)
kw_dict = {key: value} kw_dict = {key: value}
return kw_dict return kw_dict
def build_List(self, o): def build_List(self, o):
return map(self.build, o.getChildren()) return map(self.build, o.getChildren())
@ -377,7 +378,39 @@ class _Builder3:
def build_Index(self, o): def build_Index(self, o):
return self.build(o.value) return self.build(o.value)
def _build_call35(self, o):
"""
Workaround for python 3.5 _ast.Call signature, docs found here
https://greentreesnakes.readthedocs.org/en/latest/nodes.html
"""
import ast
callee = self.build(o.func)
args = []
if o.args is not None:
for a in o.args:
if isinstance(a, ast.Starred):
args.append(self.build(a.value))
else:
args.append(self.build(a))
kwargs = {}
for kw in o.keywords:
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.")
# give preference to the keys set directly from arg=value
for k, v in rst.items():
if k not in kwargs:
kwargs[k] = v
else: # defined on the call as: arg=value
kwargs[kw.arg] = self.build(kw.value)
return callee(*args, **kwargs)
def build_Call(self, o): def build_Call(self, o):
if sys.version_info >= (3, 5):
return self._build_call35(o)
callee = self.build(o.func) callee = self.build(o.func)
if o.args is None: if o.args is None:
@ -388,13 +421,16 @@ class _Builder3:
if o.starargs is None: if o.starargs is None:
starargs = () starargs = ()
else: else:
starargs = self.build(o.starargs) starargs = tuple(self.build(o.starargs))
if o.kwargs is None: if o.kwargs is None:
kwargs = {} kwargs = {}
else: else:
kwargs = self.build(o.kwargs) kwargs = self.build(o.kwargs)
if o.keywords is not None: # direct a=b keywords
for kw in o.keywords:
# preference because is a direct keyword against **kwargs
kwargs[kw.arg] = self.build(kw.value)
return callee(*(args + starargs), **kwargs) return callee(*(args + starargs), **kwargs)
def build_List(self, o): def build_List(self, o):

5
cherrypy/lib/static.py

@ -49,7 +49,10 @@ def serve_file(path, content_type=None, disposition=None, name=None,
try: try:
st = os.stat(path) st = os.stat(path)
except OSError: except (OSError, TypeError, ValueError):
# OSError when file fails to stat
# TypeError on Python 2 when there's a null byte
# ValueError on Python 3 when there's a null byte
if debug: if debug:
cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC') cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC')
raise cherrypy.NotFound() raise cherrypy.NotFound()

35
cherrypy/process/plugins.py

@ -8,7 +8,7 @@ import time
import threading import threading
from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident
from cherrypy._cpcompat import ntob, set, Timer, SetDaemonProperty from cherrypy._cpcompat import ntob, Timer, SetDaemonProperty
# _module__file__base is used by Autoreload to make # _module__file__base is used by Autoreload to make
# absolute any filenames retrieved from sys.modules which are not # absolute any filenames retrieved from sys.modules which are not
@ -109,12 +109,35 @@ class SignalHandler(object):
self.handlers['SIGINT'] = self._jython_SIGINT_handler self.handlers['SIGINT'] = self._jython_SIGINT_handler
self._previous_handlers = {} self._previous_handlers = {}
# used to determine is the process is a daemon in `self._is_daemonized`
self._original_pid = os.getpid()
def _jython_SIGINT_handler(self, signum=None, frame=None): def _jython_SIGINT_handler(self, signum=None, frame=None):
# See http://bugs.jython.org/issue1313 # See http://bugs.jython.org/issue1313
self.bus.log('Keyboard Interrupt: shutting down bus') self.bus.log('Keyboard Interrupt: shutting down bus')
self.bus.exit() self.bus.exit()
def _is_daemonized(self):
"""Return boolean indicating if the current process is
running as a daemon.
The criteria to determine the `daemon` condition is to verify
if the current pid is not the same as the one that got used on
the initial construction of the plugin *and* the stdin is not
connected to a terminal.
The sole validation of the tty is not enough when the plugin
is executing inside other process like in a CI tool
(Buildbot, Jenkins).
"""
if (self._original_pid != os.getpid() and
not os.isatty(sys.stdin.fileno())):
return True
else:
return False
def subscribe(self): def subscribe(self):
"""Subscribe self.handlers to signals.""" """Subscribe self.handlers to signals."""
for sig, func in self.handlers.items(): for sig, func in self.handlers.items():
@ -180,13 +203,13 @@ class SignalHandler(object):
def handle_SIGHUP(self): def handle_SIGHUP(self):
"""Restart if daemonized, else exit.""" """Restart if daemonized, else exit."""
if os.isatty(sys.stdin.fileno()): if self._is_daemonized():
self.bus.log("SIGHUP caught while daemonized. Restarting.")
self.bus.restart()
else:
# not daemonized (may be foreground or background) # 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() self.bus.exit()
else:
self.bus.log("SIGHUP caught while daemonized. Restarting.")
self.bus.restart()
try: try:
@ -200,7 +223,7 @@ class DropPrivileges(SimplePlugin):
"""Drop privileges. uid/gid arguments not available on Windows. """Drop privileges. uid/gid arguments not available on Windows.
Special thanks to `Gavin Baker <http://antonym.org/2005/12/dropping-privileges-in-python.html>`_ Special thanks to `Gavin Baker <http://antonym.org/2005/12/dropping-privileges-in-python.html>`_
""" """
def __init__(self, bus, umask=None, uid=None, gid=None): def __init__(self, bus, umask=None, uid=None, gid=None):

3
cherrypy/process/servers.py

@ -183,8 +183,7 @@ class ServerAdapter(object):
if not self.httpserver: if not self.httpserver:
return '' return ''
host, port = self.bind_addr host, port = self.bind_addr
if getattr(self.httpserver, 'ssl_certificate', None) or \ if getattr(self.httpserver, 'ssl_adapter', None):
getattr(self.httpserver, 'ssl_adapter', None):
scheme = "https" scheme = "https"
if port != 443: if port != 443:
host += ":%s" % port host += ":%s" % port

2
cherrypy/process/wspbus.py

@ -68,8 +68,6 @@ import time
import traceback as _traceback import traceback as _traceback
import warnings import warnings
from cherrypy._cpcompat import set
# Here I save the value of os.getcwd(), which, if I am imported early enough, # 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 # will be the directory from which the startup script was run. This is needed
# by _do_execv(), to change back to the original directory before execv()ing a # by _do_execv(), to change back to the original directory before execv()ing a

4
cherrypy/wsgiserver/ssl_pyopenssl.py

@ -68,7 +68,7 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
time.sleep(self.ssl_retry) time.sleep(self.ssl_retry)
except SSL.WantWriteError: except SSL.WantWriteError:
time.sleep(self.ssl_retry) time.sleep(self.ssl_retry)
except SSL.SysCallError, e: except SSL.SysCallError as e:
if is_reader and e.args == (-1, 'Unexpected EOF'): if is_reader and e.args == (-1, 'Unexpected EOF'):
return "" return ""
@ -76,7 +76,7 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
if is_reader and errnum in wsgiserver.socket_errors_to_ignore: if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
return "" return ""
raise socket.error(errnum) raise socket.error(errnum)
except SSL.Error, e: except SSL.Error as e:
if is_reader and e.args == (-1, 'Unexpected EOF'): if is_reader and e.args == (-1, 'Unexpected EOF'):
return "" return ""

138
cherrypy/wsgiserver/wsgiserver2.py

@ -75,8 +75,8 @@ __all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
'WorkerThread', 'ThreadPool', 'SSLAdapter', 'WorkerThread', 'ThreadPool', 'SSLAdapter',
'CherryPyWSGIServer', 'CherryPyWSGIServer',
'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0', 'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
'socket_errors_to_ignore', 'WSGIPathInfoDispatcher', 'get_ssl_adapter_class',
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class', 'redirect_url'] 'socket_errors_to_ignore', 'redirect_url']
import os import os
try: try:
@ -84,19 +84,33 @@ try:
except: except:
import Queue as queue import Queue as queue
import re import re
import rfc822 import email.utils
import socket import socket
import sys import sys
import threading
import time
import traceback as traceback_
import operator
from urllib import unquote
import warnings
import errno
import logging
try:
# prefer slower Python-based io module
import _pyio as io
except ImportError:
# Python 2.6
import io
if 'win' in sys.platform and hasattr(socket, "AF_INET6"): if 'win' in sys.platform and hasattr(socket, "AF_INET6"):
if not hasattr(socket, 'IPPROTO_IPV6'): if not hasattr(socket, 'IPPROTO_IPV6'):
socket.IPPROTO_IPV6 = 41 socket.IPPROTO_IPV6 = 41
if not hasattr(socket, 'IPV6_V6ONLY'): if not hasattr(socket, 'IPV6_V6ONLY'):
socket.IPV6_V6ONLY = 27 socket.IPV6_V6ONLY = 27
try:
import cStringIO as StringIO
except ImportError: DEFAULT_BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE
import StringIO
DEFAULT_BUFFER_SIZE = -1
REDIRECT_URL = None # Application can write its HTTP-->HTTPS redirection URL here REDIRECT_URL = None # Application can write its HTTP-->HTTPS redirection URL here
@ -111,23 +125,6 @@ _fileobject_uses_str_type = isinstance(
socket._fileobject(FauxSocket())._rbuf, basestring) socket._fileobject(FauxSocket())._rbuf, basestring)
del FauxSocket # this class is not longer required for anything. del FauxSocket # this class is not longer required for anything.
import threading
import time
import traceback
def format_exc(limit=None):
"""Like print_exc() but return a string. Backport for Python 2.3."""
try:
etype, value, tb = sys.exc_info()
return ''.join(traceback.format_exception(etype, value, tb, limit))
finally:
etype = value = tb = None
import operator
from urllib import unquote
import warnings
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
bytestr = bytes bytestr = bytes
@ -167,8 +164,6 @@ ASTERISK = ntob('*')
FORWARD_SLASH = ntob('/') FORWARD_SLASH = ntob('/')
quoted_slash = re.compile(ntob("(?i)%2F")) quoted_slash = re.compile(ntob("(?i)%2F"))
import errno
def redirect_url(url=None): def redirect_url(url=None):
global REDIRECT_URL global REDIRECT_URL
if url and '%s' in url: if url and '%s' in url:
@ -218,7 +213,6 @@ comma_separated_headers = [
] ]
import logging
if not hasattr(logging, 'statistics'): if not hasattr(logging, 'statistics'):
logging.statistics = {} logging.statistics = {}
@ -682,6 +676,10 @@ class HTTPRequest(object):
# uri may be an abs_path (including "http://host.domain.tld"); # uri may be an abs_path (including "http://host.domain.tld");
scheme, authority, path = self.parse_request_uri(uri) scheme, authority, path = self.parse_request_uri(uri)
if path is None:
self.simple_response("400 Bad Request",
"Invalid path in Request-URI.")
return False
if path and NUMBER_SIGN in path: if path and NUMBER_SIGN in path:
self.simple_response("400 Bad Request", self.simple_response("400 Bad Request",
"Illegal #fragment in Request-URI.") "Illegal #fragment in Request-URI.")
@ -694,21 +692,20 @@ class HTTPRequest(object):
if path and QUESTION_MARK in path: if path and QUESTION_MARK in path:
path, qs = path.split(QUESTION_MARK, 1) path, qs = path.split(QUESTION_MARK, 1)
if path is not None: # Unquote the path+params (e.g. "/this%20path" -> "/this path").
# Unquote the path+params (e.g. "/this%20path" -> "/this path"). # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 #
# # But note that "...a URI must be separated into its components
# But note that "...a URI must be separated into its components # before the escaped characters within those components can be
# before the escaped characters within those components can be # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
# safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 # Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
# Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path". try:
try: atoms = [unquote(x) for x in quoted_slash.split(path)]
atoms = [unquote(x) for x in quoted_slash.split(path)] except ValueError:
except ValueError: ex = sys.exc_info()[1]
ex = sys.exc_info()[1] self.simple_response("400 Bad Request", ex.args[0])
self.simple_response("400 Bad Request", ex.args[0]) return False
return False path = "%2F".join(atoms)
path = "%2F".join(atoms)
self.path = path self.path = path
# Note that, like wsgiref and most other HTTP servers, # Note that, like wsgiref and most other HTTP servers,
@ -892,10 +889,7 @@ class HTTPRequest(object):
buf = [self.server.protocol + SPACE + buf = [self.server.protocol + SPACE +
status + CRLF, status + CRLF,
"Content-Length: %s\r\n" % len(msg), "Content-Length: %s\r\n" % len(msg),
"Content-Type: text/plain\r\n"] "Content-Type: text/html\r\n" if status[:3] == '301' else "Content-Type: text/plain\r\n"]
if status[:3] in ("301",):
buf.append("Location: %s" % msg)
if status[:3] in ("413", "414"): if status[:3] in ("413", "414"):
# Request Entity Too Large / Request-URI Too Long # Request Entity Too Large / Request-URI Too Long
@ -986,7 +980,7 @@ class HTTPRequest(object):
self.rfile.read(remaining) self.rfile.read(remaining)
if "date" not in hkeys: if "date" not in hkeys:
self.outheaders.append(("Date", rfc822.formatdate())) self.outheaders.append(("Date", email.utils.formatdate()))
if "server" not in hkeys: if "server" not in hkeys:
self.outheaders.append(("Server", self.server.server_name)) self.outheaders.append(("Server", self.server.server_name))
@ -1067,7 +1061,7 @@ class CP_fileobject(socket._fileobject):
if size < 0: if size < 0:
# Read until EOF # Read until EOF
# reset _rbuf. we consume it via buf. # reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO() self._rbuf = io.BytesIO()
while True: while True:
data = self.recv(rbufsize) data = self.recv(rbufsize)
if not data: if not data:
@ -1082,12 +1076,12 @@ class CP_fileobject(socket._fileobject):
# return. # return.
buf.seek(0) buf.seek(0)
rv = buf.read(size) rv = buf.read(size)
self._rbuf = StringIO.StringIO() self._rbuf = io.BytesIO()
self._rbuf.write(buf.read()) self._rbuf.write(buf.read())
return rv return rv
# reset _rbuf. we consume it via buf. # reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO() self._rbuf = io.BytesIO()
while True: while True:
left = size - buf_len left = size - buf_len
# recv() will malloc the amount of memory given as its # recv() will malloc the amount of memory given as its
@ -1125,7 +1119,7 @@ class CP_fileobject(socket._fileobject):
buf.seek(0) buf.seek(0)
bline = buf.readline(size) bline = buf.readline(size)
if bline.endswith('\n') or len(bline) == size: if bline.endswith('\n') or len(bline) == size:
self._rbuf = StringIO.StringIO() self._rbuf = io.BytesIO()
self._rbuf.write(buf.read()) self._rbuf.write(buf.read())
return bline return bline
del bline del bline
@ -1136,7 +1130,7 @@ class CP_fileobject(socket._fileobject):
buf.seek(0) buf.seek(0)
buffers = [buf.read()] buffers = [buf.read()]
# reset _rbuf. we consume it via buf. # reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO() self._rbuf = io.BytesIO()
data = None data = None
recv = self.recv recv = self.recv
while data != "\n": while data != "\n":
@ -1148,7 +1142,7 @@ class CP_fileobject(socket._fileobject):
buf.seek(0, 2) # seek end buf.seek(0, 2) # seek end
# reset _rbuf. we consume it via buf. # reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO() self._rbuf = io.BytesIO()
while True: while True:
data = self.recv(self._rbufsize) data = self.recv(self._rbufsize)
if not data: if not data:
@ -1170,11 +1164,11 @@ class CP_fileobject(socket._fileobject):
if buf_len >= size: if buf_len >= size:
buf.seek(0) buf.seek(0)
rv = buf.read(size) rv = buf.read(size)
self._rbuf = StringIO.StringIO() self._rbuf = io.BytesIO()
self._rbuf.write(buf.read()) self._rbuf.write(buf.read())
return rv return rv
# reset _rbuf. we consume it via buf. # reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO() self._rbuf = io.BytesIO()
while True: while True:
data = self.recv(self._rbufsize) data = self.recv(self._rbufsize)
if not data: if not data:
@ -1411,7 +1405,10 @@ class HTTPConnection(object):
self.wfile = CP_fileobject( self.wfile = CP_fileobject(
self.socket._sock, "wb", self.wbufsize) self.socket._sock, "wb", self.wbufsize)
if REDIRECT_URL: if REDIRECT_URL:
req.simple_response("301 Moved Permanently", REDIRECT_URL % self.remote_addr) msg = '<!DOCTYPE html><html><head>' \
'<meta http-equiv="refresh" content="0; url=%s">' \
'</head><body></body></html>' % (REDIRECT_URL % self.remote_addr)
req.simple_response("301 Moved Permanently", msg)
else: else:
req.simple_response( req.simple_response(
"400 Bad Request", "400 Bad Request",
@ -1776,7 +1773,7 @@ class HTTPServer(object):
timeout = 10 timeout = 10
"""The timeout in seconds for accepted connections (default 10).""" """The timeout in seconds for accepted connections (default 10)."""
version = "CherryPy/3.8.0" version = "CherryPy/5.1.0"
"""A version string for the HTTPServer.""" """A version string for the HTTPServer."""
software = None software = None
@ -1903,25 +1900,6 @@ class HTTPServer(object):
if self.software is None: if self.software is None:
self.software = "%s Server" % self.version self.software = "%s Server" % self.version
# SSL backward compatibility
if (self.ssl_adapter is None and
getattr(self, 'ssl_certificate', None) and
getattr(self, 'ssl_private_key', None)):
warnings.warn(
"SSL attributes are deprecated in CherryPy 3.2, and will "
"be removed in CherryPy 3.3. Use an ssl_adapter attribute "
"instead.",
DeprecationWarning
)
try:
from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
except ImportError:
pass
else:
self.ssl_adapter = pyOpenSSLAdapter(
self.ssl_certificate, self.ssl_private_key,
getattr(self, 'ssl_certificate_chain', None))
# Select the appropriate socket # Select the appropriate socket
if isinstance(self.bind_addr, basestring): if isinstance(self.bind_addr, basestring):
# AF_UNIX socket # AF_UNIX socket
@ -1934,7 +1912,7 @@ class HTTPServer(object):
# So everyone can access the socket... # So everyone can access the socket...
try: try:
os.chmod(self.bind_addr, 511) # 0777 os.chmod(self.bind_addr, 0o777)
except: except:
pass pass
@ -2003,7 +1981,7 @@ class HTTPServer(object):
sys.stderr.write(msg + '\n') sys.stderr.write(msg + '\n')
sys.stderr.flush() sys.stderr.flush()
if traceback: if traceback:
tblines = format_exc() tblines = traceback_.format_exc()
sys.stderr.write(tblines) sys.stderr.write(tblines)
sys.stderr.flush() sys.stderr.flush()
@ -2205,7 +2183,7 @@ ssl_adapters = {
} }
def get_ssl_adapter_class(name='pyopenssl'): def get_ssl_adapter_class(name='builtin'):
"""Return an SSL adapter class for the given name.""" """Return an SSL adapter class for the given name."""
adapter = ssl_adapters[name.lower()] adapter = ssl_adapters[name.lower()]
if isinstance(adapter, basestring): if isinstance(adapter, basestring):

39
cherrypy/wsgiserver/wsgiserver3.py

@ -75,7 +75,8 @@ __all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
'WorkerThread', 'ThreadPool', 'SSLAdapter', 'WorkerThread', 'ThreadPool', 'SSLAdapter',
'CherryPyWSGIServer', 'CherryPyWSGIServer',
'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0', 'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class'] 'WSGIPathInfoDispatcher', 'get_ssl_adapter_class',
'socket_errors_to_ignore']
import os import os
try: try:
@ -86,20 +87,28 @@ import re
import email.utils import email.utils
import socket import socket
import sys import sys
import threading
import time
import traceback as traceback_
import errno
import logging
try:
# prefer slower Python-based io module
import _pyio as io
except ImportError:
# Python 2.6
import io
if 'win' in sys.platform and hasattr(socket, "AF_INET6"): if 'win' in sys.platform and hasattr(socket, "AF_INET6"):
if not hasattr(socket, 'IPPROTO_IPV6'): if not hasattr(socket, 'IPPROTO_IPV6'):
socket.IPPROTO_IPV6 = 41 socket.IPPROTO_IPV6 = 41
if not hasattr(socket, 'IPV6_V6ONLY'): if not hasattr(socket, 'IPV6_V6ONLY'):
socket.IPV6_V6ONLY = 27 socket.IPV6_V6ONLY = 27
if sys.version_info < (3, 1):
import io
else:
import _pyio as io
DEFAULT_BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE DEFAULT_BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE
import threading
import time
from traceback import format_exc
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
bytestr = bytes bytestr = bytes
@ -139,8 +148,6 @@ ASTERISK = ntob('*')
FORWARD_SLASH = ntob('/') FORWARD_SLASH = ntob('/')
quoted_slash = re.compile(ntob("(?i)%2F")) quoted_slash = re.compile(ntob("(?i)%2F"))
import errno
def plat_specific_errors(*errnames): def plat_specific_errors(*errnames):
"""Return error numbers for all errors in errnames on this platform. """Return error numbers for all errors in errnames on this platform.
@ -184,7 +191,6 @@ comma_separated_headers = [
] ]
import logging
if not hasattr(logging, 'statistics'): if not hasattr(logging, 'statistics'):
logging.statistics = {} logging.statistics = {}
@ -650,6 +656,10 @@ class HTTPRequest(object):
# uri may be an abs_path (including "http://host.domain.tld"); # uri may be an abs_path (including "http://host.domain.tld");
scheme, authority, path = self.parse_request_uri(uri) scheme, authority, path = self.parse_request_uri(uri)
if path is None:
self.simple_response("400 Bad Request",
"Invalid path in Request-URI.")
return False
if NUMBER_SIGN in path: if NUMBER_SIGN in path:
self.simple_response("400 Bad Request", self.simple_response("400 Bad Request",
"Illegal #fragment in Request-URI.") "Illegal #fragment in Request-URI.")
@ -1468,7 +1478,7 @@ class HTTPServer(object):
timeout = 10 timeout = 10
"""The timeout in seconds for accepted connections (default 10).""" """The timeout in seconds for accepted connections (default 10)."""
version = "CherryPy/3.8.0" version = "CherryPy/5.1.0"
"""A version string for the HTTPServer.""" """A version string for the HTTPServer."""
software = None software = None
@ -1608,7 +1618,7 @@ class HTTPServer(object):
# So everyone can access the socket... # So everyone can access the socket...
try: try:
os.chmod(self.bind_addr, 511) # 0777 os.chmod(self.bind_addr, 0o777)
except: except:
pass pass
@ -1676,7 +1686,7 @@ class HTTPServer(object):
sys.stderr.write(msg + '\n') sys.stderr.write(msg + '\n')
sys.stderr.flush() sys.stderr.flush()
if traceback: if traceback:
tblines = format_exc() tblines = traceback_.format_exc()
sys.stderr.write(tblines) sys.stderr.write(tblines)
sys.stderr.flush() sys.stderr.flush()
@ -1874,6 +1884,7 @@ class Gateway(object):
# of such classes (in which case they will be lazily loaded). # of such classes (in which case they will be lazily loaded).
ssl_adapters = { ssl_adapters = {
'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter', 'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
} }

Loading…
Cancel
Save