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.
This file has been added.
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>`_.
"""
__version__ = "3.8.0"
__version__ = "5.1.0"
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 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__)
try:
set = set
except NameError:
from sets import Set as set
try:
# Python 3.1+
from base64 import decodebytes as _base64_decodebytes
except ImportError:
@ -137,17 +132,6 @@ def base64_decode(n, encoding='ISO-8859-1'):
else:
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:
sorted = sorted
@ -333,18 +317,10 @@ except ImportError:
# In Python 3, pickle is the sped-up C version.
import pickle
try:
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.
import binascii
def random20():
return sha('%s' % random.random()).hexdigest()
def random20():
return binascii.hexlify(os.urandom(20)).decode('ascii')
try:
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.wShowWindow = _subprocess.SW_HIDE
comspec = os.environ.get("COMSPEC", "cmd.exe")
args = '{} /c "{}"'.format(comspec, args)
args = '{0} /c "{1}"'.format(comspec, args)
if (_subprocess.GetVersion() >= 0x80000000 or
os.path.basename(comspec).lower() == "command.com"):
# Win9x, or using command.com on NT. We need to
@ -1029,7 +1029,7 @@ class Popen(object):
elif sig == signal.CTRL_BREAK_EVENT:
os.kill(self.pid, signal.CTRL_BREAK_EVENT)
else:
raise ValueError("Unsupported signal: {}".format(sig))
raise ValueError("Unsupported signal: {0}".format(sig))
def terminate(self):
"""Terminates the process

2
cherrypy/_cpconfig.py

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

1
cherrypy/_cpdispatch.py

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

3
cherrypy/_cpwsgi.py

@ -296,7 +296,8 @@ class AppResponse(object):
"""Create a Request object using environ."""
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', ''))
remote = httputil.Host(env('REMOTE_ADDR', ''),
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()
addr = cherrypy.server.bind_addr
if fastcgi:
f = servers.FlupFCGIServer(application=cherrypy.tree,
bindAddress=addr)
elif scgi:
f = servers.FlupSCGIServer(application=cherrypy.tree,
bindAddress=addr)
else:
f = servers.FlupCGIServer(application=cherrypy.tree,
bindAddress=addr)
cls = (
servers.FlupFCGIServer if fastcgi else
servers.FlupSCGIServer if scgi else
servers.FlupCGIServer
)
f = cls(application=cherrypy.tree, bindAddress=addr)
s = servers.ServerAdapter(engine, httpserver=f, bind_addr=addr)
s.subscribe()

3
cherrypy/lib/auth_digest.py

@ -23,10 +23,11 @@ __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 md5, ntob
from cherrypy._cpcompat import ntob
md5_hex = lambda s: md5(ntob(s)).hexdigest()
qop_auth = 'auth'

18
cherrypy/lib/cpstats.py

@ -210,6 +210,7 @@ def extrapolate_statistics(scope):
# -------------------- CherryPy Applications Statistics --------------------- #
import sys
import threading
import time
@ -294,6 +295,11 @@ class ByteCountWrapper(object):
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):
"""Record various information about the current request."""
@ -322,7 +328,7 @@ class StatsTool(cherrypy.Tool):
appstats['Current Requests'] += 1
appstats['Total Requests'] += 1
appstats['Requests'][threading._get_ident()] = {
appstats['Requests'][_get_threading_ident()] = {
'Bytes Read': None,
'Bytes Written': None,
# Use a lambda so the ip gets updated by tools.proxy later
@ -339,7 +345,7 @@ class StatsTool(cherrypy.Tool):
debug=False, **kwargs):
"""Record the end of a request."""
resp = cherrypy.serving.response
w = appstats['Requests'][threading._get_ident()]
w = appstats['Requests'][_get_threading_ident()]
r = cherrypy.request.rfile.bytes_read
w['Bytes Read'] = r
@ -605,7 +611,13 @@ table.stats2 th {
"""Return ([headers], [rows]) for the given collection."""
# E.g., the 'Requests' dict.
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:
format = formatting.get(k3, missing)
if format is None:

10
cherrypy/lib/cptools.py

@ -2,9 +2,10 @@
import logging
import re
from hashlib import md5
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 is_iterator
@ -192,11 +193,10 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
if lbase is not None:
base = lbase.split(',')[0]
if not base:
base = request.headers.get('Host', '127.0.0.1')
port = request.local.port
if port == 80:
base = '127.0.0.1'
else:
base = '127.0.0.1:%s' % port
if port != 80:
base += ':%s' % port
if base.find("://") == -1:
# add http:// or https:// if needed

2
cherrypy/lib/encoding.py

@ -2,7 +2,7 @@ import struct
import time
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 is_closable_iterator
from cherrypy.lib import set_vary_header

4
cherrypy/lib/httpauth.py

@ -62,7 +62,9 @@ __all__ = ("digestAuth", "basicAuth", "doAuth", "checkResponse",
##########################################################################
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
MD5 = "MD5"

1
cherrypy/lib/httputil.py

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

2
cherrypy/lib/profiler.py

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

42
cherrypy/lib/reprconf.py

@ -281,13 +281,14 @@ class _Builder2:
# Everything else becomes args
else :
args.append(self.build(child))
return callee(*args, **kwargs)
def build_Keyword(self, o):
key, value_obj = o.getChildren()
value = self.build(value_obj)
kw_dict = {key: value}
return kw_dict
return kw_dict
def build_List(self, o):
return map(self.build, o.getChildren())
@ -377,7 +378,39 @@ class _Builder3:
def build_Index(self, o):
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):
if sys.version_info >= (3, 5):
return self._build_call35(o)
callee = self.build(o.func)
if o.args is None:
@ -388,13 +421,16 @@ class _Builder3:
if o.starargs is None:
starargs = ()
else:
starargs = self.build(o.starargs)
starargs = tuple(self.build(o.starargs))
if o.kwargs is None:
kwargs = {}
else:
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)
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:
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:
cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC')
raise cherrypy.NotFound()

35
cherrypy/process/plugins.py

@ -8,7 +8,7 @@ import time
import threading
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
# 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._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):
# See http://bugs.jython.org/issue1313
self.bus.log('Keyboard Interrupt: shutting down bus')
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):
"""Subscribe self.handlers to signals."""
for sig, func in self.handlers.items():
@ -180,13 +203,13 @@ class SignalHandler(object):
def handle_SIGHUP(self):
"""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)
self.bus.log("SIGHUP caught but not daemonized. Exiting.")
self.bus.exit()
else:
self.bus.log("SIGHUP caught while daemonized. Restarting.")
self.bus.restart()
try:
@ -200,7 +223,7 @@ class DropPrivileges(SimplePlugin):
"""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):

3
cherrypy/process/servers.py

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

2
cherrypy/process/wspbus.py

@ -68,8 +68,6 @@ import time
import traceback as _traceback
import warnings
from cherrypy._cpcompat import set
# 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
# 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)
except SSL.WantWriteError:
time.sleep(self.ssl_retry)
except SSL.SysCallError, e:
except SSL.SysCallError as e:
if is_reader and e.args == (-1, 'Unexpected EOF'):
return ""
@ -76,7 +76,7 @@ class SSL_fileobject(wsgiserver.CP_fileobject):
if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
return ""
raise socket.error(errnum)
except SSL.Error, e:
except SSL.Error as e:
if is_reader and e.args == (-1, 'Unexpected EOF'):
return ""

138
cherrypy/wsgiserver/wsgiserver2.py

@ -75,8 +75,8 @@ __all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
'WorkerThread', 'ThreadPool', 'SSLAdapter',
'CherryPyWSGIServer',
'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
'socket_errors_to_ignore',
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class', 'redirect_url']
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class',
'socket_errors_to_ignore', 'redirect_url']
import os
try:
@ -84,19 +84,33 @@ try:
except:
import Queue as queue
import re
import rfc822
import email.utils
import socket
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 not hasattr(socket, 'IPPROTO_IPV6'):
socket.IPPROTO_IPV6 = 41
if not hasattr(socket, 'IPV6_V6ONLY'):
socket.IPV6_V6ONLY = 27
try:
import cStringIO as StringIO
except ImportError:
import StringIO
DEFAULT_BUFFER_SIZE = -1
DEFAULT_BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE
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)
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):
bytestr = bytes
@ -167,8 +164,6 @@ ASTERISK = ntob('*')
FORWARD_SLASH = ntob('/')
quoted_slash = re.compile(ntob("(?i)%2F"))
import errno
def redirect_url(url=None):
global REDIRECT_URL
if url and '%s' in url:
@ -218,7 +213,6 @@ comma_separated_headers = [
]
import logging
if not hasattr(logging, 'statistics'):
logging.statistics = {}
@ -682,6 +676,10 @@ class HTTPRequest(object):
# uri may be an abs_path (including "http://host.domain.tld");
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:
self.simple_response("400 Bad Request",
"Illegal #fragment in Request-URI.")
@ -694,21 +692,20 @@ class HTTPRequest(object):
if path and QUESTION_MARK in path:
path, qs = path.split(QUESTION_MARK, 1)
if path is not None:
# Unquote the path+params (e.g. "/this%20path" -> "/this path").
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
#
# But note that "...a URI must be separated into its components
# before the escaped characters within those components can be
# safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
# Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
try:
atoms = [unquote(x) for x in quoted_slash.split(path)]
except ValueError:
ex = sys.exc_info()[1]
self.simple_response("400 Bad Request", ex.args[0])
return False
path = "%2F".join(atoms)
# Unquote the path+params (e.g. "/this%20path" -> "/this path").
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
#
# But note that "...a URI must be separated into its components
# before the escaped characters within those components can be
# safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
# Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
try:
atoms = [unquote(x) for x in quoted_slash.split(path)]
except ValueError:
ex = sys.exc_info()[1]
self.simple_response("400 Bad Request", ex.args[0])
return False
path = "%2F".join(atoms)
self.path = path
# Note that, like wsgiref and most other HTTP servers,
@ -892,10 +889,7 @@ class HTTPRequest(object):
buf = [self.server.protocol + SPACE +
status + CRLF,
"Content-Length: %s\r\n" % len(msg),
"Content-Type: text/plain\r\n"]
if status[:3] in ("301",):
buf.append("Location: %s" % msg)
"Content-Type: text/html\r\n" if status[:3] == '301' else "Content-Type: text/plain\r\n"]
if status[:3] in ("413", "414"):
# Request Entity Too Large / Request-URI Too Long
@ -986,7 +980,7 @@ class HTTPRequest(object):
self.rfile.read(remaining)
if "date" not in hkeys:
self.outheaders.append(("Date", rfc822.formatdate()))
self.outheaders.append(("Date", email.utils.formatdate()))
if "server" not in hkeys:
self.outheaders.append(("Server", self.server.server_name))
@ -1067,7 +1061,7 @@ class CP_fileobject(socket._fileobject):
if size < 0:
# Read until EOF
# reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO()
self._rbuf = io.BytesIO()
while True:
data = self.recv(rbufsize)
if not data:
@ -1082,12 +1076,12 @@ class CP_fileobject(socket._fileobject):
# return.
buf.seek(0)
rv = buf.read(size)
self._rbuf = StringIO.StringIO()
self._rbuf = io.BytesIO()
self._rbuf.write(buf.read())
return rv
# reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO()
self._rbuf = io.BytesIO()
while True:
left = size - buf_len
# recv() will malloc the amount of memory given as its
@ -1125,7 +1119,7 @@ class CP_fileobject(socket._fileobject):
buf.seek(0)
bline = buf.readline(size)
if bline.endswith('\n') or len(bline) == size:
self._rbuf = StringIO.StringIO()
self._rbuf = io.BytesIO()
self._rbuf.write(buf.read())
return bline
del bline
@ -1136,7 +1130,7 @@ class CP_fileobject(socket._fileobject):
buf.seek(0)
buffers = [buf.read()]
# reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO()
self._rbuf = io.BytesIO()
data = None
recv = self.recv
while data != "\n":
@ -1148,7 +1142,7 @@ class CP_fileobject(socket._fileobject):
buf.seek(0, 2) # seek end
# reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO()
self._rbuf = io.BytesIO()
while True:
data = self.recv(self._rbufsize)
if not data:
@ -1170,11 +1164,11 @@ class CP_fileobject(socket._fileobject):
if buf_len >= size:
buf.seek(0)
rv = buf.read(size)
self._rbuf = StringIO.StringIO()
self._rbuf = io.BytesIO()
self._rbuf.write(buf.read())
return rv
# reset _rbuf. we consume it via buf.
self._rbuf = StringIO.StringIO()
self._rbuf = io.BytesIO()
while True:
data = self.recv(self._rbufsize)
if not data:
@ -1411,7 +1405,10 @@ class HTTPConnection(object):
self.wfile = CP_fileobject(
self.socket._sock, "wb", self.wbufsize)
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:
req.simple_response(
"400 Bad Request",
@ -1776,7 +1773,7 @@ class HTTPServer(object):
timeout = 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."""
software = None
@ -1903,25 +1900,6 @@ class HTTPServer(object):
if self.software is None:
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
if isinstance(self.bind_addr, basestring):
# AF_UNIX socket
@ -1934,7 +1912,7 @@ class HTTPServer(object):
# So everyone can access the socket...
try:
os.chmod(self.bind_addr, 511) # 0777
os.chmod(self.bind_addr, 0o777)
except:
pass
@ -2003,7 +1981,7 @@ class HTTPServer(object):
sys.stderr.write(msg + '\n')
sys.stderr.flush()
if traceback:
tblines = format_exc()
tblines = traceback_.format_exc()
sys.stderr.write(tblines)
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."""
adapter = ssl_adapters[name.lower()]
if isinstance(adapter, basestring):

39
cherrypy/wsgiserver/wsgiserver3.py

@ -75,7 +75,8 @@ __all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
'WorkerThread', 'ThreadPool', 'SSLAdapter',
'CherryPyWSGIServer',
'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class']
'WSGIPathInfoDispatcher', 'get_ssl_adapter_class',
'socket_errors_to_ignore']
import os
try:
@ -86,20 +87,28 @@ import re
import email.utils
import socket
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 not hasattr(socket, 'IPPROTO_IPV6'):
socket.IPPROTO_IPV6 = 41
if not hasattr(socket, 'IPV6_V6ONLY'):
socket.IPV6_V6ONLY = 27
if sys.version_info < (3, 1):
import io
else:
import _pyio as io
DEFAULT_BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE
import threading
import time
from traceback import format_exc
if sys.version_info >= (3, 0):
bytestr = bytes
@ -139,8 +148,6 @@ ASTERISK = ntob('*')
FORWARD_SLASH = ntob('/')
quoted_slash = re.compile(ntob("(?i)%2F"))
import errno
def plat_specific_errors(*errnames):
"""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'):
logging.statistics = {}
@ -650,6 +656,10 @@ class HTTPRequest(object):
# uri may be an abs_path (including "http://host.domain.tld");
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:
self.simple_response("400 Bad Request",
"Illegal #fragment in Request-URI.")
@ -1468,7 +1478,7 @@ class HTTPServer(object):
timeout = 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."""
software = None
@ -1608,7 +1618,7 @@ class HTTPServer(object):
# So everyone can access the socket...
try:
os.chmod(self.bind_addr, 511) # 0777
os.chmod(self.bind_addr, 0o777)
except:
pass
@ -1676,7 +1686,7 @@ class HTTPServer(object):
sys.stderr.write(msg + '\n')
sys.stderr.flush()
if traceback:
tblines = format_exc()
tblines = traceback_.format_exc()
sys.stderr.write(tblines)
sys.stderr.flush()
@ -1874,6 +1884,7 @@ class Gateway(object):
# of such classes (in which case they will be lazily loaded).
ssl_adapters = {
'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
}

Loading…
Cancel
Save