From 277202cf7e479371c5f4f0d25005386fc1b5f1fa Mon Sep 17 00:00:00 2001 From: Philipp Hahn Date: Fri, 4 Oct 2024 16:12:02 +0200 Subject: [PATCH] Xmlrpc: accept data:str|bytes and require method:str (#12734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declared Inside [`do_POST()`](https://github.com/python/cpython/blob/main/Lib/xmlrpc/server.py#L493) `data: bytes`, where `decode_request_content()` only handles `gzip` compression. The `result` is then written to `self.wfile`, which uses `bytes`. But for [CGI](https://github.com/python/cpython/blob/main/Lib/xmlrpc/server.py#L636) `str` is used: [`handle_xmlrpc(self, request_text)`](https://github.com/python/cpython/blob/main/Lib/xmlrpc/server.py#L642) calls the argument `request_text`, which is read from `sys.stdin` as type `str` and then passed to `_marshaled_dispatch()`, which internally calls [`xmlrpc.client.loads()`](https://github.com/python/cpython/blob/main/Lib/xmlrpc/server.py#L257) to parse the XML using Expat, which accepts both `str` and `bytes`; the later defaults to encoding `utf-8`, but other encodings can be used when explicitly specified: >>> xmlrpc.client.loads('ä') (('ä',), None) >>> xmlrpc.client.loads('ä'.encode("utf-8")) (('ä',), None) >>> xmlrpc.client.loads('ä'.encode("iso-8859-1")) Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.11/xmlrpc/client.py", line 1029, in loads p.feed(data) File "/usr/lib/python3.11/xmlrpc/client.py", line 451, in feed self._parser.Parse(data, False) xml.parsers.expat.ExpatError: not well-formed (invalid token): line 3, column 15 >>> xmlrpc.client.loads('ä'.encode("iso-8859-1")) Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.11/xmlrpc/client.py", line 1029, in loads p.feed(data) File "/usr/lib/python3.11/xmlrpc/client.py", line 451, in feed self._parser.Parse(data, False) xml.parsers.expat.ExpatError: not well-formed (invalid token): line 1, column 68 >>> xmlrpc.client.loads('ä'.encode("iso-8859-1")) (('ä',), None) Signed-off-by: Philipp Hahn Reviewed-By: Jelle Zijlstra --- stdlib/xmlrpc/client.pyi | 2 +- stdlib/xmlrpc/server.pyi | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/stdlib/xmlrpc/client.pyi b/stdlib/xmlrpc/client.pyi index d254102ac..5899d1d72 100644 --- a/stdlib/xmlrpc/client.pyi +++ b/stdlib/xmlrpc/client.pyi @@ -200,7 +200,7 @@ def dumps( allow_none: bool = False, ) -> str: ... def loads( - data: str, use_datetime: bool = False, use_builtin_types: bool = False + data: str | ReadableBuffer, use_datetime: bool = False, use_builtin_types: bool = False ) -> tuple[tuple[_Marshallable, ...], str | None]: ... def gzip_encode(data: ReadableBuffer) -> bytes: ... # undocumented def gzip_decode(data: ReadableBuffer, max_decode: int = 20971520) -> bytes: ... # undocumented diff --git a/stdlib/xmlrpc/server.pyi b/stdlib/xmlrpc/server.pyi index 8ca3a4d1a..5f497aa71 100644 --- a/stdlib/xmlrpc/server.pyi +++ b/stdlib/xmlrpc/server.pyi @@ -1,6 +1,7 @@ import http.server import pydoc import socketserver +from _typeshed import ReadableBuffer from collections.abc import Callable, Iterable, Mapping from re import Pattern from typing import Any, ClassVar, Protocol @@ -48,8 +49,8 @@ class SimpleXMLRPCDispatcher: # undocumented def register_multicall_functions(self) -> None: ... def _marshaled_dispatch( self, - data: str, - dispatch_method: Callable[[str | None, tuple[_Marshallable, ...]], Fault | tuple[_Marshallable, ...]] | None = None, + data: str | ReadableBuffer, + dispatch_method: Callable[[str, tuple[_Marshallable, ...]], Fault | tuple[_Marshallable, ...]] | None = None, path: Any | None = None, ) -> str: ... # undocumented def system_listMethods(self) -> list[str]: ... # undocumented