Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions Doc/c-api/descriptor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,46 @@ found in the dictionary of type objects.
.. c:function:: PyObject* PyDescr_NewMember(PyTypeObject *type, struct PyMemberDef *meth)


.. c:var:: PyTypeObject PyMemberDescr_Type

The type object for member descriptor objects created from
:c:type:`PyMemberDef` structures. These descriptors expose fields of a
C struct as attributes on a type, and correspond
to :class:`types.MemberDescriptorType` objects in Python.



.. c:var:: PyTypeObject PyGetSetDescr_Type

The type object for get/set descriptor objects created from
:c:type:`PyGetSetDef` structures. These descriptors implement attributes
whose value is computed by C getter and setter functions, and are used
for many built-in type attributes.


.. c:function:: PyObject* PyDescr_NewMethod(PyTypeObject *type, struct PyMethodDef *meth)


.. c:var:: PyTypeObject PyMethodDescr_Type

The type object for method descriptor objects created from
:c:type:`PyMethodDef` structures. These descriptors expose C functions as
methods on a type, and correspond to :class:`types.MemberDescriptorType`
objects in Python.


.. c:function:: PyObject* PyDescr_NewWrapper(PyTypeObject *type, struct wrapperbase *wrapper, void *wrapped)


.. c:var:: PyTypeObject PyWrapperDescr_Type

The type object for wrapper descriptor objects created by
:c:func:`PyDescr_NewWrapper` and :c:func:`PyWrapper_New`. Wrapper
descriptors are used internally to expose special methods implemented
via wrapper structures, and appear in Python as
:class:`types.WrapperDescriptorType` objects.


.. c:function:: PyObject* PyDescr_NewClassMethod(PyTypeObject *type, PyMethodDef *method)


Expand Down Expand Up @@ -55,6 +89,14 @@ Built-in descriptors
:class:`classmethod` in the Python layer.


.. c:var:: PyTypeObject PyClassMethodDescr_Type

The type object for C-level class method descriptor objects.
This is the type of the descriptors created for :func:`classmethod` defined in
C extension types, and is the same object as :class:`classmethod`
in Python.


.. c:function:: PyObject *PyClassMethod_New(PyObject *callable)

Create a new :class:`classmethod` object wrapping *callable*.
Expand Down
11 changes: 11 additions & 0 deletions Doc/c-api/dict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ Dictionary Objects
prevent modification of the dictionary for non-dynamic class types.


.. c:var:: PyTypeObject PyDictProxy_Type

The type object for mapping proxy objects created by
:c:func:`PyDictProxy_New` and for the read-only ``__dict__`` attribute
of many built-in types. A :c:type:`PyDictProxy_Type` instance provides a
dynamic, read-only view of an underlying dictionary: changes to the
underlying dictionary are reflected in the proxy, but the proxy itself
does not support mutation operations. This corresponds to
:class:`types.MappingProxyType` in Python.


.. c:function:: void PyDict_Clear(PyObject *p)

Empty an existing dictionary of all key-value pairs.
Expand Down
24 changes: 24 additions & 0 deletions Doc/using/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,30 @@ General Options

.. versionadded:: 3.11

.. option:: --with-missing-stdlib-config=FILE

Path to a `JSON <https://www.json.org/json-en.html>`_ configuration file
containing custom error messages for missing :term:`standard library` modules.

This option is intended for Python distributors who wish to provide
distribution-specific guidance when users encounter standard library
modules that are missing or packaged separately.

The JSON file should map missing module names to custom error message strings.
For example, if your distribution packages :mod:`tkinter` and
:mod:`_tkinter` separately and excludes :mod:`!_gdbm` for legal reasons,
the configuration could contain:

.. code-block:: json

{
"_gdbm": "The '_gdbm' module is not available in this distribution"
"tkinter": "Install the python-tk package to use tkinter",
"_tkinter": "Install the python-tk package to use tkinter",
}

.. versionadded:: next

.. option:: --enable-pystats

Turn on internal Python performance statistics gathering.
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,12 @@ Build changes
set to ``no`` or with :option:`!--without-system-libmpdec`.
(Contributed by Sergey B Kirpichev in :gh:`115119`.)

* The new configure option :option:`--with-missing-stdlib-config=FILE` allows
distributors to pass a `JSON <https://www.json.org/json-en.html>`_
configuration file containing custom error messages for :term:`standard library`
modules that are missing or packaged separately.
(Contributed by Stan Ulbrych and Petr Viktorin in :gh:`139707`.)


Porting to Python 3.15
======================
Expand Down
28 changes: 24 additions & 4 deletions Lib/http/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@
_MAXLINE = 65536
_MAXHEADERS = 100

# Data larger than this will be read in chunks, to prevent extreme
# overallocation.
_MIN_READ_BUF_SIZE = 1 << 20


# Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2)
#
# VCHAR = %x21-7E
Expand Down Expand Up @@ -642,10 +647,25 @@ def _safe_read(self, amt):
reading. If the bytes are truly not available (due to EOF), then the
IncompleteRead exception can be used to detect the problem.
"""
data = self.fp.read(amt)
if len(data) < amt:
raise IncompleteRead(data, amt-len(data))
return data
cursize = min(amt, _MIN_READ_BUF_SIZE)
data = self.fp.read(cursize)
if len(data) >= amt:
return data
if len(data) < cursize:
raise IncompleteRead(data, amt - len(data))

data = io.BytesIO(data)
data.seek(0, 2)
while True:
# This is a geometric increase in read size (never more than
# doubling out the current length of data per loop iteration).
delta = min(cursize, amt - cursize)
data.write(self.fp.read(delta))
if data.tell() >= amt:
return data.getvalue()
cursize += delta
if data.tell() < cursize:
raise IncompleteRead(data.getvalue(), amt - data.tell())

def _safe_readinto(self, b):
"""Same as _safe_read, but for reading into a buffer."""
Expand Down
31 changes: 20 additions & 11 deletions Lib/plistlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)
globals().update(PlistFormat.__members__)

# Data larger than this will be read in chunks, to prevent extreme
# overallocation.
_MIN_READ_BUF_SIZE = 1 << 20

class UID:
def __init__(self, data):
Expand Down Expand Up @@ -508,12 +511,24 @@ def _get_size(self, tokenL):

return tokenL

def _read(self, size):
cursize = min(size, _MIN_READ_BUF_SIZE)
data = self._fp.read(cursize)
while True:
if len(data) != cursize:
raise InvalidFileException
if cursize == size:
return data
delta = min(cursize, size - cursize)
data += self._fp.read(delta)
cursize += delta

def _read_ints(self, n, size):
data = self._fp.read(size * n)
data = self._read(size * n)
if size in _BINARY_FORMAT:
return struct.unpack(f'>{n}{_BINARY_FORMAT[size]}', data)
else:
if not size or len(data) != size * n:
if not size:
raise InvalidFileException()
return tuple(int.from_bytes(data[i: i + size], 'big')
for i in range(0, size * n, size))
Expand Down Expand Up @@ -573,22 +588,16 @@ def _read_object(self, ref):

elif tokenH == 0x40: # data
s = self._get_size(tokenL)
result = self._fp.read(s)
if len(result) != s:
raise InvalidFileException()
result = self._read(s)

elif tokenH == 0x50: # ascii string
s = self._get_size(tokenL)
data = self._fp.read(s)
if len(data) != s:
raise InvalidFileException()
data = self._read(s)
result = data.decode('ascii')

elif tokenH == 0x60: # unicode string
s = self._get_size(tokenL) * 2
data = self._fp.read(s)
if len(data) != s:
raise InvalidFileException()
data = self._read(s)
result = data.decode('utf-16be')

elif tokenH == 0x80: # UID
Expand Down
66 changes: 66 additions & 0 deletions Lib/test/test_httplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,72 @@ def run_server():
thread.join()
self.assertEqual(result, b"proxied data\n")

def test_large_content_length(self):
serv = socket.create_server((HOST, 0))
self.addCleanup(serv.close)

def run_server():
[conn, address] = serv.accept()
with conn:
while conn.recv(1024):
conn.sendall(
b"HTTP/1.1 200 Ok\r\n"
b"Content-Length: %d\r\n"
b"\r\n" % size)
conn.sendall(b'A' * (size//3))
conn.sendall(b'B' * (size - size//3))

thread = threading.Thread(target=run_server)
thread.start()
self.addCleanup(thread.join, 1.0)

conn = client.HTTPConnection(*serv.getsockname())
try:
for w in range(15, 27):
size = 1 << w
conn.request("GET", "/")
with conn.getresponse() as response:
self.assertEqual(len(response.read()), size)
finally:
conn.close()
thread.join(1.0)

def test_large_content_length_truncated(self):
serv = socket.create_server((HOST, 0))
self.addCleanup(serv.close)

def run_server():
while True:
[conn, address] = serv.accept()
with conn:
conn.recv(1024)
if not size:
break
conn.sendall(
b"HTTP/1.1 200 Ok\r\n"
b"Content-Length: %d\r\n"
b"\r\n"
b"Text" % size)

thread = threading.Thread(target=run_server)
thread.start()
self.addCleanup(thread.join, 1.0)

conn = client.HTTPConnection(*serv.getsockname())
try:
for w in range(18, 65):
size = 1 << w
conn.request("GET", "/")
with conn.getresponse() as response:
self.assertRaises(client.IncompleteRead, response.read)
conn.close()
finally:
conn.close()
size = 0
conn.request("GET", "/")
conn.close()
thread.join(1.0)

def test_putrequest_override_domain_validation(self):
"""
It should be possible to override the default validation
Expand Down
37 changes: 34 additions & 3 deletions Lib/test/test_plistlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -903,8 +903,7 @@ def test_dump_naive_datetime_with_aware_datetime_option(self):

class TestBinaryPlistlib(unittest.TestCase):

@staticmethod
def decode(*objects, offset_size=1, ref_size=1):
def build(self, *objects, offset_size=1, ref_size=1):
data = [b'bplist00']
offset = 8
offsets = []
Expand All @@ -916,7 +915,11 @@ def decode(*objects, offset_size=1, ref_size=1):
len(objects), 0, offset)
data.extend(offsets)
data.append(tail)
return plistlib.loads(b''.join(data), fmt=plistlib.FMT_BINARY)
return b''.join(data)

def decode(self, *objects, offset_size=1, ref_size=1):
data = self.build(*objects, offset_size=offset_size, ref_size=ref_size)
return plistlib.loads(data, fmt=plistlib.FMT_BINARY)

def test_nonstandard_refs_size(self):
# Issue #21538: Refs and offsets are 24-bit integers
Expand Down Expand Up @@ -1024,6 +1027,34 @@ def test_invalid_binary(self):
with self.assertRaises(plistlib.InvalidFileException):
plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY)

def test_truncated_large_data(self):
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
def check(data):
with open(os_helper.TESTFN, 'wb') as f:
f.write(data)
# buffered file
with open(os_helper.TESTFN, 'rb') as f:
with self.assertRaises(plistlib.InvalidFileException):
plistlib.load(f, fmt=plistlib.FMT_BINARY)
# unbuffered file
with open(os_helper.TESTFN, 'rb', buffering=0) as f:
with self.assertRaises(plistlib.InvalidFileException):
plistlib.load(f, fmt=plistlib.FMT_BINARY)
for w in range(20, 64):
s = 1 << w
# data
check(self.build(b'\x4f\x13' + s.to_bytes(8, 'big')))
# ascii string
check(self.build(b'\x5f\x13' + s.to_bytes(8, 'big')))
# unicode string
check(self.build(b'\x6f\x13' + s.to_bytes(8, 'big')))
# array
check(self.build(b'\xaf\x13' + s.to_bytes(8, 'big')))
# dict
check(self.build(b'\xdf\x13' + s.to_bytes(8, 'big')))
# number of objects
check(b'bplist00' + struct.pack('>6xBBQQQ', 1, 1, s, 0, 8))

def test_load_aware_datetime(self):
data = (b'bplist003B\x04>\xd0d\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00'
b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00'
Expand Down
23 changes: 22 additions & 1 deletion Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -5051,7 +5051,7 @@ def test_no_site_package_flavour(self):
b"or to enable your virtual environment?"), stderr
)

def test_missing_stdlib_package(self):
def test_missing_stdlib_module(self):
code = """
import sys
sys.stdlib_module_names |= {'spam'}
Expand All @@ -5061,6 +5061,27 @@ def test_missing_stdlib_package(self):

self.assertIn(b"Standard library module 'spam' was not found", stderr)

code = """
import sys
import traceback
traceback._MISSING_STDLIB_MODULE_MESSAGES = {'spam': "Install 'spam4life' for 'spam'"}
sys.stdlib_module_names |= {'spam'}
import spam
"""
_, _, stderr = assert_python_failure('-S', '-c', code)

self.assertIn(b"Install 'spam4life' for 'spam'", stderr)

@unittest.skipIf(sys.platform == "win32", "Non-Windows test")
def test_windows_only_module_error(self):
try:
import msvcrt # noqa: F401
except ModuleNotFoundError:
formatted = traceback.format_exc()
self.assertIn("Unsupported platform for Windows-only standard library module 'msvcrt'", formatted)
else:
self.fail("ModuleNotFoundError was not raised")


class TestColorizedTraceback(unittest.TestCase):
maxDiff = None
Expand Down
Loading
Loading