Update cherrypy-18.6.1

This commit is contained in:
JonnyWong16 2021-10-14 21:17:18 -07:00
commit ebffd124f6
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
57 changed files with 1269 additions and 1509 deletions

View file

@ -70,6 +70,11 @@ class file_generator(object):
raise StopIteration()
next = __next__
def __del__(self):
"""Close input on descturct."""
if hasattr(self.input, 'close'):
self.input.close()
def file_generator_limited(fileobj, count, chunk_size=65536):
"""Yield the given file object in chunks.

View file

@ -23,8 +23,7 @@ of plaintext passwords as the credentials store::
import time
import functools
from hashlib import md5
from six.moves.urllib.request import parse_http_list, parse_keqv_list
from urllib.request import parse_http_list, parse_keqv_list
import cherrypy
from cherrypy._cpcompat import ntob, tonative

View file

@ -37,11 +37,8 @@ import sys
import threading
import time
import six
import cherrypy
from cherrypy.lib import cptools, httputil
from cherrypy._cpcompat import Event
class Cache(object):
@ -82,7 +79,7 @@ class AntiStampedeCache(dict):
If timeout is None, no waiting is performed nor sentinels used.
"""
value = self.get(key)
if isinstance(value, Event):
if isinstance(value, threading.Event):
if timeout is None:
# Ignore the other thread and recalc it ourselves.
if debug:
@ -122,7 +119,7 @@ class AntiStampedeCache(dict):
"""Set the cached value for the given key."""
existing = self.get(key)
dict.__setitem__(self, key, value)
if isinstance(existing, Event):
if isinstance(existing, threading.Event):
# Set Event.result so other threads waiting on it have
# immediate access without needing to poll the cache again.
existing.result = value
@ -199,8 +196,7 @@ class MemoryCache(Cache):
now = time.time()
# Must make a copy of expirations so it doesn't change size
# during iteration
items = list(six.iteritems(self.expirations))
for expiration_time, objects in items:
for expiration_time, objects in self.expirations.copy().items():
if expiration_time <= now:
for obj_size, uri, sel_header_values in objects:
try:

View file

@ -25,8 +25,7 @@ import sys
import cgi
import os
import os.path
from six.moves import urllib
import urllib.parse
import cherrypy

View file

@ -193,10 +193,8 @@ import sys
import threading
import time
import six
import cherrypy
from cherrypy._cpcompat import json
from cherrypy._json import json
# ------------------------------- Statistics -------------------------------- #
@ -207,7 +205,7 @@ if not hasattr(logging, 'statistics'):
def extrapolate_statistics(scope):
"""Return an extrapolated copy of the given scope."""
c = {}
for k, v in list(scope.items()):
for k, v in scope.copy().items():
if isinstance(v, dict):
v = extrapolate_statistics(v)
elif isinstance(v, (list, tuple)):
@ -366,8 +364,8 @@ class StatsTool(cherrypy.Tool):
w['Bytes Written'] = cl
appstats['Total Bytes Written'] += cl
w['Response Status'] = getattr(
resp, 'output_status', None) or resp.status
w['Response Status'] = \
getattr(resp, 'output_status', resp.status).decode()
w['End Time'] = time.time()
p = w['End Time'] - w['Start Time']
@ -613,7 +611,7 @@ table.stats2 th {
"""Return ([headers], [rows]) for the given collection."""
# E.g., the 'Requests' dict.
headers = []
vals = six.itervalues(v)
vals = v.values()
for record in vals:
for k3 in record:
format = formatting.get(k3, missing)
@ -679,7 +677,7 @@ table.stats2 th {
def data(self):
s = extrapolate_statistics(logging.statistics)
cherrypy.response.headers['Content-Type'] = 'application/json'
return json.dumps(s, sort_keys=True, indent=4)
return json.dumps(s, sort_keys=True, indent=4).encode('utf-8')
@cherrypy.expose
def pause(self, namespace):

View file

@ -3,9 +3,7 @@
import logging
import re
from hashlib import md5
import six
from six.moves import urllib
import urllib.parse
import cherrypy
from cherrypy._cpcompat import text_or_bytes
@ -307,7 +305,7 @@ class SessionAuth(object):
def login_screen(self, from_page='..', username='', error_msg='',
**kwargs):
return (six.text_type("""<html><body>
return (str("""<html><body>
Message: %(error_msg)s
<form method="post" action="do_login">
Login: <input type="text" name="username" value="%(username)s" size="10" />
@ -406,23 +404,22 @@ Message: %(error_msg)s
def session_auth(**kwargs):
"""Session authentication hook.
Any attribute of the SessionAuth class may be overridden
via a keyword arg to this function:
""" + '\n '.join(
'{!s}: {!s}'.format(k, type(getattr(SessionAuth, k)).__name__)
for k in dir(SessionAuth)
if not k.startswith('__')
)
sa = SessionAuth()
for k, v in kwargs.items():
setattr(sa, k, v)
return sa.run()
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('__')])
)
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)

View file

@ -2,8 +2,6 @@ import struct
import time
import io
import six
import cherrypy
from cherrypy._cpcompat import text_or_bytes
from cherrypy.lib import file_generator
@ -11,6 +9,10 @@ from cherrypy.lib import is_closable_iterator
from cherrypy.lib import set_vary_header
_COMPRESSION_LEVEL_FAST = 1
_COMPRESSION_LEVEL_BEST = 9
def decode(encoding=None, default_encoding='utf-8'):
"""Replace or extend the list of charsets used to decode a request entity.
@ -50,7 +52,7 @@ class UTF8StreamEncoder:
def __next__(self):
res = next(self._iterator)
if isinstance(res, six.text_type):
if isinstance(res, str):
res = res.encode('utf-8')
return res
@ -99,7 +101,7 @@ class ResponseEncoder:
def encoder(body):
for chunk in body:
if isinstance(chunk, six.text_type):
if isinstance(chunk, str):
chunk = chunk.encode(encoding, self.errors)
yield chunk
self.body = encoder(self.body)
@ -112,7 +114,7 @@ class ResponseEncoder:
self.attempted_charsets.add(encoding)
body = []
for chunk in self.body:
if isinstance(chunk, six.text_type):
if isinstance(chunk, str):
try:
chunk = chunk.encode(encoding, self.errors)
except (LookupError, UnicodeError):
@ -287,13 +289,29 @@ def compress(body, compress_level):
"""Compress 'body' at the given compress_level."""
import zlib
# See http://www.gzip.org/zlib/rfc-gzip.html
# See https://tools.ietf.org/html/rfc1952
yield b'\x1f\x8b' # ID1 and ID2: gzip marker
yield b'\x08' # CM: compression method
yield b'\x00' # FLG: none set
# MTIME: 4 bytes
yield struct.pack('<L', int(time.time()) & int('FFFFFFFF', 16))
yield b'\x02' # XFL: max compression, slowest algo
# RFC 1952, section 2.3.1:
#
# XFL (eXtra FLags)
# These flags are available for use by specific compression
# methods. The "deflate" method (CM = 8) sets these flags as
# follows:
#
# XFL = 2 - compressor used maximum compression,
# slowest algorithm
# XFL = 4 - compressor used fastest algorithm
if compress_level == _COMPRESSION_LEVEL_BEST:
yield b'\x02' # XFL: max compression, slowest algo
elif compress_level == _COMPRESSION_LEVEL_FAST:
yield b'\x04' # XFL: min compression, fastest algo
else:
yield b'\x00' # XFL: compression unset/tradeoff
yield b'\xff' # OS: unknown
crc = zlib.crc32(b'')

View file

@ -10,17 +10,17 @@ to a public caning.
import functools
import email.utils
import re
import builtins
from binascii import b2a_base64
from cgi import parse_header
from email.header import decode_header
from http.server import BaseHTTPRequestHandler
from urllib.parse import unquote_plus
import six
from six.moves import range, builtins, map
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler
import jaraco.collections
import cherrypy
from cherrypy._cpcompat import ntob, ntou
from cherrypy._cpcompat import unquote_plus
response_codes = BaseHTTPRequestHandler.responses.copy()
@ -143,7 +143,7 @@ class HeaderElement(object):
return self.value < other.value
def __str__(self):
p = [';%s=%s' % (k, v) for k, v in six.iteritems(self.params)]
p = [';%s=%s' % (k, v) for k, v in self.params.items()]
return str('%s%s' % (self.value, ''.join(p)))
def __bytes__(self):
@ -209,14 +209,11 @@ class AcceptElement(HeaderElement):
Ref: https://github.com/cherrypy/cherrypy/issues/1370
"""
six.raise_from(
cherrypy.HTTPError(
400,
'Malformed HTTP header: `{}`'.
format(str(self)),
),
val_err,
)
raise cherrypy.HTTPError(
400,
'Malformed HTTP header: `{}`'.
format(str(self)),
) from val_err
def __cmp__(self, other):
diff = builtins.cmp(self.qvalue, other.qvalue)
@ -283,11 +280,11 @@ def valid_status(status):
If status has no reason-phrase is supplied, a default reason-
phrase will be provided.
>>> from six.moves import http_client
>>> from six.moves.BaseHTTPServer import BaseHTTPRequestHandler
>>> valid_status(http_client.ACCEPTED) == (
... int(http_client.ACCEPTED),
... ) + BaseHTTPRequestHandler.responses[http_client.ACCEPTED]
>>> import http.client
>>> from http.server import BaseHTTPRequestHandler
>>> valid_status(http.client.ACCEPTED) == (
... int(http.client.ACCEPTED),
... ) + BaseHTTPRequestHandler.responses[http.client.ACCEPTED]
True
"""
@ -295,7 +292,7 @@ def valid_status(status):
status = 200
code, reason = status, None
if isinstance(status, six.string_types):
if isinstance(status, str):
code, _, reason = status.partition(' ')
reason = reason.strip() or None
@ -390,77 +387,19 @@ def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'):
return pm
####
# Inlined from jaraco.collections 1.5.2
# Ref #1673
class KeyTransformingDict(dict):
"""
A dict subclass that transforms the keys before they're used.
Subclasses may override the default transform_key to customize behavior.
"""
@staticmethod
def transform_key(key):
return key
def __init__(self, *args, **kargs):
super(KeyTransformingDict, self).__init__()
# build a dictionary using the default constructs
d = dict(*args, **kargs)
# build this dictionary using transformed keys.
for item in d.items():
self.__setitem__(*item)
def __setitem__(self, key, val):
key = self.transform_key(key)
super(KeyTransformingDict, self).__setitem__(key, val)
def __getitem__(self, key):
key = self.transform_key(key)
return super(KeyTransformingDict, self).__getitem__(key)
def __contains__(self, key):
key = self.transform_key(key)
return super(KeyTransformingDict, self).__contains__(key)
def __delitem__(self, key):
key = self.transform_key(key)
return super(KeyTransformingDict, self).__delitem__(key)
def get(self, key, *args, **kwargs):
key = self.transform_key(key)
return super(KeyTransformingDict, self).get(key, *args, **kwargs)
def setdefault(self, key, *args, **kwargs):
key = self.transform_key(key)
return super(KeyTransformingDict, self).setdefault(
key, *args, **kwargs)
def pop(self, key, *args, **kwargs):
key = self.transform_key(key)
return super(KeyTransformingDict, self).pop(key, *args, **kwargs)
def matching_key_for(self, key):
"""
Given a key, return the actual key stored in self that matches.
Raise KeyError if the key isn't found.
"""
try:
return next(e_key for e_key in self.keys() if e_key == key)
except StopIteration:
raise KeyError(key)
####
class CaseInsensitiveDict(KeyTransformingDict):
class CaseInsensitiveDict(jaraco.collections.KeyTransformingDict):
"""A case-insensitive dict subclass.
Each key is changed on entry to str(key).title().
Each key is changed on entry to title case.
"""
@staticmethod
def transform_key(key):
return str(key).title()
if key is None:
# TODO(#1830): why?
return 'None'
return key.title()
# TEXT = <any OCTET except CTLs, but including LWS>
@ -499,9 +438,7 @@ class HeaderMap(CaseInsensitiveDict):
def elements(self, key):
"""Return a sorted list of HeaderElements for the given header."""
key = str(key).title()
value = self.get(key)
return header_elements(key, value)
return header_elements(self.transform_key(key), self.get(key))
def values(self, key):
"""Return a sorted list of HeaderElement.value for the given header."""
@ -518,15 +455,14 @@ class HeaderMap(CaseInsensitiveDict):
transmitting on the wire for HTTP.
"""
for k, v in header_items:
if not isinstance(v, six.string_types) and \
not isinstance(v, six.binary_type):
v = six.text_type(v)
if not isinstance(v, str) and not isinstance(v, bytes):
v = str(v)
yield tuple(map(cls.encode_header_item, (k, v)))
@classmethod
def encode_header_item(cls, item):
if isinstance(item, six.text_type):
if isinstance(item, str):
item = cls.encode(item)
# See header_translate_* constants above.

View file

@ -1,5 +1,6 @@
import cherrypy
from cherrypy._cpcompat import text_or_bytes, ntou, json_encode, json_decode
from cherrypy import _json as json
from cherrypy._cpcompat import text_or_bytes, ntou
def json_processor(entity):
@ -9,7 +10,7 @@ def json_processor(entity):
body = entity.fp.read()
with cherrypy.HTTPError.handle(ValueError, 400, 'Invalid JSON document'):
cherrypy.serving.request.json = json_decode(body.decode('utf-8'))
cherrypy.serving.request.json = json.decode(body.decode('utf-8'))
def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
@ -56,7 +57,7 @@ def json_in(content_type=[ntou('application/json'), ntou('text/javascript')],
def json_handler(*args, **kwargs):
value = cherrypy.serving.request._json_inner_handler(*args, **kwargs)
return json_encode(value)
return json.encode(value)
def json_out(content_type='application/json', debug=False,

View file

@ -18,13 +18,13 @@ by adding a named handler to Config.namespaces. The name can be any string,
and the handler must be either a callable or a context manager.
"""
from cherrypy._cpcompat import text_or_bytes
from six.moves import configparser
from six.moves import builtins
import builtins
import configparser
import operator
import sys
from cherrypy._cpcompat import text_or_bytes
class NamespaceSet(dict):
@ -36,7 +36,7 @@ class NamespaceSet(dict):
namespace removed) and the config value.
Namespace handlers may be any Python callable; they may also be
Python 2.5-style 'context managers', in which case their __enter__
context managers, in which case their __enter__
method should return a callable to be used as the handler.
See cherrypy.tools (the Toolbox class) for an example.
"""
@ -61,10 +61,10 @@ class NamespaceSet(dict):
bucket[name] = config[k]
# I chose __enter__ and __exit__ so someday this could be
# rewritten using Python 2.5's 'with' statement:
# for ns, handler in six.iteritems(self):
# rewritten using 'with' statement:
# for ns, handler in self.items():
# with handler as callable:
# for k, v in six.iteritems(ns_confs.get(ns, {})):
# for k, v in ns_confs.get(ns, {}).items():
# callable(k, v)
for ns, handler in self.items():
exit = getattr(handler, '__exit__', None)
@ -211,122 +211,7 @@ class Parser(configparser.ConfigParser):
# public domain "unrepr" implementation, found on the web and then improved.
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' %
repr(o.__class__.__name__))
return m(o)
def astnode(self, s):
"""Return a Python2 ast Node compiled from a string."""
try:
import compiler
except ImportError:
# Fallback to eval when compiler package is not available,
# e.g. IronPython 1.0.
return eval(s)
p = compiler.parse('__tempvalue__ = ' + s)
return p.getChildren()[1].getChildren()[0].getChildren()[1]
def build_Subscript(self, o):
expr, flags, subs = o.getChildren()
expr = self.build(expr)
subs = self.build(subs)
return expr[subs]
def build_CallFunc(self, o):
children = o.getChildren()
# Build callee from first child
callee = self.build(children[0])
# Build args and kwargs from remaining children
args = []
kwargs = {}
for child in children[1:]:
class_name = child.__class__.__name__
# None is ignored
if class_name == 'NoneType':
continue
# Keywords become kwargs
if class_name == 'Keyword':
kwargs.update(self.build(child))
# 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
def build_List(self, o):
return map(self.build, o.getChildren())
def build_Const(self, o):
return o.value
def build_Dict(self, o):
d = {}
i = iter(map(self.build, o.getChildren()))
for el in i:
d[el] = i.next()
return d
def build_Tuple(self, o):
return tuple(self.build_List(o))
def build_Name(self, o):
name = o.name
if name == 'None':
return None
if name == 'True':
return True
if name == 'False':
return False
# See if the Name is a package or module. If it is, import it.
try:
return modules(name)
except ImportError:
pass
# See if the Name is in builtins.
try:
return getattr(builtins, name)
except AttributeError:
pass
raise TypeError('unrepr could not resolve the name %s' % repr(name))
def build_Add(self, o):
left, right = map(self.build, o.getChildren())
return left + right
def build_Mul(self, o):
left, right = map(self.build, o.getChildren())
return left * right
def build_Getattr(self, o):
parent = self.build(o.expr)
return getattr(parent, o.attrname)
def build_NoneType(self, o):
return None
def build_UnarySub(self, o):
return -self.build(o.getChildren()[0])
def build_UnaryAdd(self, o):
return self.build(o.getChildren()[0])
class _Builder3:
class _Builder:
def build(self, o):
m = getattr(self, 'build_' + o.__class__.__name__, None)
@ -441,7 +326,6 @@ class _Builder3:
# See if the Name is in builtins.
try:
import builtins
return getattr(builtins, name)
except AttributeError:
pass
@ -482,10 +366,7 @@ def unrepr(s):
"""Return a Python object compiled from a string."""
if not s:
return s
if sys.version_info < (3, 0):
b = _Builder2()
else:
b = _Builder3()
b = _Builder()
obj = b.astnode(s)
return b.build(obj)

View file

@ -106,10 +106,7 @@ import os
import time
import threading
import binascii
import six
from six.moves import cPickle as pickle
import contextlib2
import pickle
import zc.lockfile
@ -119,10 +116,6 @@ from cherrypy.lib import locking
from cherrypy.lib import is_iterator
if six.PY2:
FileNotFoundError = OSError
missing = object()
@ -410,7 +403,7 @@ class RamSession(Session):
"""Clean up expired sessions."""
now = self.now()
for _id, (data, expiration_time) in list(six.iteritems(self.cache)):
for _id, (data, expiration_time) in self.cache.copy().items():
if expiration_time <= now:
try:
del self.cache[_id]
@ -572,8 +565,6 @@ class FileSession(Session):
def release_lock(self, path=None):
"""Release the lock on the currently-loaded session data."""
self.lock.close()
with contextlib2.suppress(FileNotFoundError):
os.remove(self.lock._path)
self.locked = False
def clean_up(self):
@ -624,7 +615,7 @@ class MemcachedSession(Session):
# This is a separate set of locks per session id.
locks = {}
servers = ['127.0.0.1:11211']
servers = ['localhost:11211']
@classmethod
def setup(cls, **kwargs):

View file

@ -5,12 +5,12 @@ import platform
import re
import stat
import mimetypes
import urllib.parse
import unicodedata
from email.generator import _make_boundary as make_boundary
from io import UnsupportedOperation
from six.moves import urllib
import cherrypy
from cherrypy._cpcompat import ntob
from cherrypy.lib import cptools, httputil, file_generator_limited
@ -29,6 +29,30 @@ def _setup_mimetypes():
_setup_mimetypes()
def _make_content_disposition(disposition, file_name):
"""Create HTTP header for downloading a file with a UTF-8 filename.
This function implements the recommendations of :rfc:`6266#appendix-D`.
See this and related answers: https://stackoverflow.com/a/8996249/2173868.
"""
# As normalization algorithm for `unicodedata` is used composed form (NFC
# and NFKC) with compatibility equivalence criteria (NFK), so "NFKC" is the
# one. It first applies the compatibility decomposition, followed by the
# canonical composition. Should be displayed in the same manner, should be
# treated in the same way by applications such as alphabetizing names or
# searching, and may be substituted for each other.
# See: https://en.wikipedia.org/wiki/Unicode_equivalence.
ascii_name = (
unicodedata.normalize('NFKC', file_name).
encode('ascii', errors='ignore').decode()
)
header = '{}; filename="{}"'.format(disposition, ascii_name)
if ascii_name != file_name:
quoted_name = urllib.parse.quote(file_name)
header += '; filename*=UTF-8\'\'{}'.format(quoted_name)
return header
def serve_file(path, content_type=None, disposition=None, name=None,
debug=False):
"""Set status, headers, and body in order to serve the given path.
@ -38,9 +62,10 @@ def serve_file(path, content_type=None, disposition=None, name=None,
of the 'path' argument.
If disposition is not None, the Content-Disposition header will be set
to "<disposition>; filename=<name>". If name is None, it will be set
to the basename of path. If disposition is None, no Content-Disposition
header will be written.
to "<disposition>; filename=<name>; filename*=utf-8''<name>"
as described in :rfc:`6266#appendix-D`.
If name is None, it will be set to the basename of path.
If disposition is None, no Content-Disposition header will be written.
"""
response = cherrypy.serving.response
@ -93,7 +118,7 @@ def serve_file(path, content_type=None, disposition=None, name=None,
if disposition is not None:
if name is None:
name = os.path.basename(path)
cd = '%s; filename="%s"' % (disposition, name)
cd = _make_content_disposition(disposition, name)
response.headers['Content-Disposition'] = cd
if debug:
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')
@ -112,9 +137,10 @@ def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
The Content-Type header will be set to the content_type arg, if provided.
If disposition is not None, the Content-Disposition header will be set
to "<disposition>; filename=<name>". If name is None, 'filename' will
not be set. If disposition is None, no Content-Disposition header will
be written.
to "<disposition>; filename=<name>; filename*=utf-8''<name>"
as described in :rfc:`6266#appendix-D`.
If name is None, 'filename' will not be set.
If disposition is None, no Content-Disposition header will be written.
CAUTION: If the request contains a 'Range' header, one or more seek()s will
be performed on the file object. This may cause undesired behavior if
@ -150,7 +176,7 @@ def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
if name is None:
cd = disposition
else:
cd = '%s; filename="%s"' % (disposition, name)
cd = _make_content_disposition(disposition, name)
response.headers['Content-Disposition'] = cd
if debug:
cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC')

View file

@ -1,7 +1,6 @@
"""XML-RPC tool helpers."""
import sys
from six.moves.xmlrpc_client import (
from xmlrpc.client import (
loads as xmlrpc_loads, dumps as xmlrpc_dumps,
Fault as XMLRPCFault
)