mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2025-12-15 15:28:50 -05:00
Merge remote-tracking branch 'upstream/release-v1.40'
This commit is contained in:
commit
7359964d9f
124 changed files with 3989 additions and 904 deletions
|
|
@ -848,7 +848,7 @@ class _ReadBodyWithMaxSizeProtocol(protocol.Protocol):
|
|||
|
||||
def read_body_with_max_size(
|
||||
response: IResponse, stream: ByteWriteable, max_size: Optional[int]
|
||||
) -> defer.Deferred:
|
||||
) -> "defer.Deferred[int]":
|
||||
"""
|
||||
Read a HTTP response body to a file-object. Optionally enforcing a maximum file size.
|
||||
|
||||
|
|
@ -863,7 +863,7 @@ def read_body_with_max_size(
|
|||
Returns:
|
||||
A Deferred which resolves to the length of the read body.
|
||||
"""
|
||||
d = defer.Deferred()
|
||||
d: "defer.Deferred[int]" = defer.Deferred()
|
||||
|
||||
# If the Content-Length header gives a size larger than the maximum allowed
|
||||
# size, do not bother downloading the body.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from twisted.internet.interfaces import (
|
|||
)
|
||||
from twisted.web.client import URI, Agent, HTTPConnectionPool
|
||||
from twisted.web.http_headers import Headers
|
||||
from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer
|
||||
from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IResponse
|
||||
|
||||
from synapse.crypto.context_factory import FederationPolicyForHTTPS
|
||||
from synapse.http.client import BlacklistingAgentWrapper
|
||||
|
|
@ -116,7 +116,7 @@ class MatrixFederationAgent:
|
|||
uri: bytes,
|
||||
headers: Optional[Headers] = None,
|
||||
bodyProducer: Optional[IBodyProducer] = None,
|
||||
) -> Generator[defer.Deferred, Any, defer.Deferred]:
|
||||
) -> Generator[defer.Deferred, Any, IResponse]:
|
||||
"""
|
||||
Args:
|
||||
method: HTTP method: GET/POST/etc
|
||||
|
|
|
|||
|
|
@ -14,21 +14,32 @@
|
|||
import base64
|
||||
import logging
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
from urllib.request import getproxies_environment, proxy_bypass_environment
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import ( # type: ignore[attr-defined]
|
||||
getproxies_environment,
|
||||
proxy_bypass_environment,
|
||||
)
|
||||
|
||||
import attr
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS
|
||||
from twisted.internet.interfaces import IReactorCore, IStreamClientEndpoint
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.web.client import URI, BrowserLikePolicyForHTTPS, _AgentBase
|
||||
from twisted.web.client import (
|
||||
URI,
|
||||
BrowserLikePolicyForHTTPS,
|
||||
HTTPConnectionPool,
|
||||
_AgentBase,
|
||||
)
|
||||
from twisted.web.error import SchemeNotSupported
|
||||
from twisted.web.http_headers import Headers
|
||||
from twisted.web.iweb import IAgent, IPolicyForHTTPS
|
||||
from twisted.web.iweb import IAgent, IBodyProducer, IPolicyForHTTPS
|
||||
|
||||
from synapse.http.connectproxyclient import HTTPConnectProxyEndpoint
|
||||
from synapse.types import ISynapseReactor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -63,35 +74,38 @@ class ProxyAgent(_AgentBase):
|
|||
reactor might have some blacklisting applied (i.e. for DNS queries),
|
||||
but we need unblocked access to the proxy.
|
||||
|
||||
contextFactory (IPolicyForHTTPS): A factory for TLS contexts, to control the
|
||||
contextFactory: A factory for TLS contexts, to control the
|
||||
verification parameters of OpenSSL. The default is to use a
|
||||
`BrowserLikePolicyForHTTPS`, so unless you have special
|
||||
requirements you can leave this as-is.
|
||||
|
||||
connectTimeout (Optional[float]): The amount of time that this Agent will wait
|
||||
connectTimeout: The amount of time that this Agent will wait
|
||||
for the peer to accept a connection, in seconds. If 'None',
|
||||
HostnameEndpoint's default (30s) will be used.
|
||||
|
||||
This is used for connections to both proxies and destination servers.
|
||||
|
||||
bindAddress (bytes): The local address for client sockets to bind to.
|
||||
bindAddress: The local address for client sockets to bind to.
|
||||
|
||||
pool (HTTPConnectionPool|None): connection pool to be used. If None, a
|
||||
pool: connection pool to be used. If None, a
|
||||
non-persistent pool instance will be created.
|
||||
|
||||
use_proxy (bool): Whether proxy settings should be discovered and used
|
||||
use_proxy: Whether proxy settings should be discovered and used
|
||||
from conventional environment variables.
|
||||
|
||||
Raises:
|
||||
ValueError if use_proxy is set and the environment variables
|
||||
contain an invalid proxy specification.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
reactor,
|
||||
proxy_reactor=None,
|
||||
reactor: IReactorCore,
|
||||
proxy_reactor: Optional[ISynapseReactor] = None,
|
||||
contextFactory: Optional[IPolicyForHTTPS] = None,
|
||||
connectTimeout=None,
|
||||
bindAddress=None,
|
||||
pool=None,
|
||||
use_proxy=False,
|
||||
connectTimeout: Optional[float] = None,
|
||||
bindAddress: Optional[bytes] = None,
|
||||
pool: Optional[HTTPConnectionPool] = None,
|
||||
use_proxy: bool = False,
|
||||
):
|
||||
contextFactory = contextFactory or BrowserLikePolicyForHTTPS()
|
||||
|
||||
|
|
@ -102,7 +116,7 @@ class ProxyAgent(_AgentBase):
|
|||
else:
|
||||
self.proxy_reactor = proxy_reactor
|
||||
|
||||
self._endpoint_kwargs = {}
|
||||
self._endpoint_kwargs: Dict[str, Any] = {}
|
||||
if connectTimeout is not None:
|
||||
self._endpoint_kwargs["timeout"] = connectTimeout
|
||||
if bindAddress is not None:
|
||||
|
|
@ -117,16 +131,12 @@ class ProxyAgent(_AgentBase):
|
|||
https_proxy = proxies["https"].encode() if "https" in proxies else None
|
||||
no_proxy = proxies["no"] if "no" in proxies else None
|
||||
|
||||
# Parse credentials from http and https proxy connection string if present
|
||||
self.http_proxy_creds, http_proxy = parse_username_password(http_proxy)
|
||||
self.https_proxy_creds, https_proxy = parse_username_password(https_proxy)
|
||||
|
||||
self.http_proxy_endpoint = _http_proxy_endpoint(
|
||||
http_proxy, self.proxy_reactor, **self._endpoint_kwargs
|
||||
self.http_proxy_endpoint, self.http_proxy_creds = _http_proxy_endpoint(
|
||||
http_proxy, self.proxy_reactor, contextFactory, **self._endpoint_kwargs
|
||||
)
|
||||
|
||||
self.https_proxy_endpoint = _http_proxy_endpoint(
|
||||
https_proxy, self.proxy_reactor, **self._endpoint_kwargs
|
||||
self.https_proxy_endpoint, self.https_proxy_creds = _http_proxy_endpoint(
|
||||
https_proxy, self.proxy_reactor, contextFactory, **self._endpoint_kwargs
|
||||
)
|
||||
|
||||
self.no_proxy = no_proxy
|
||||
|
|
@ -134,7 +144,13 @@ class ProxyAgent(_AgentBase):
|
|||
self._policy_for_https = contextFactory
|
||||
self._reactor = reactor
|
||||
|
||||
def request(self, method, uri, headers=None, bodyProducer=None):
|
||||
def request(
|
||||
self,
|
||||
method: bytes,
|
||||
uri: bytes,
|
||||
headers: Optional[Headers] = None,
|
||||
bodyProducer: Optional[IBodyProducer] = None,
|
||||
) -> defer.Deferred:
|
||||
"""
|
||||
Issue a request to the server indicated by the given uri.
|
||||
|
||||
|
|
@ -146,16 +162,15 @@ class ProxyAgent(_AgentBase):
|
|||
See also: twisted.web.iweb.IAgent.request
|
||||
|
||||
Args:
|
||||
method (bytes): The request method to use, such as `GET`, `POST`, etc
|
||||
method: The request method to use, such as `GET`, `POST`, etc
|
||||
|
||||
uri (bytes): The location of the resource to request.
|
||||
uri: The location of the resource to request.
|
||||
|
||||
headers (Headers|None): Extra headers to send with the request
|
||||
headers: Extra headers to send with the request
|
||||
|
||||
bodyProducer (IBodyProducer|None): An object which can generate bytes to
|
||||
make up the body of this request (for example, the properly encoded
|
||||
contents of a file for a file upload). Or, None if the request is to
|
||||
have no body.
|
||||
bodyProducer: An object which can generate bytes to make up the body of
|
||||
this request (for example, the properly encoded contents of a file for
|
||||
a file upload). Or, None if the request is to have no body.
|
||||
|
||||
Returns:
|
||||
Deferred[IResponse]: completes when the header of the response has
|
||||
|
|
@ -253,70 +268,89 @@ class ProxyAgent(_AgentBase):
|
|||
)
|
||||
|
||||
|
||||
def _http_proxy_endpoint(proxy: Optional[bytes], reactor, **kwargs):
|
||||
def _http_proxy_endpoint(
|
||||
proxy: Optional[bytes],
|
||||
reactor: IReactorCore,
|
||||
tls_options_factory: IPolicyForHTTPS,
|
||||
**kwargs,
|
||||
) -> Tuple[Optional[IStreamClientEndpoint], Optional[ProxyCredentials]]:
|
||||
"""Parses an http proxy setting and returns an endpoint for the proxy
|
||||
|
||||
Args:
|
||||
proxy: the proxy setting in the form: [<username>:<password>@]<host>[:<port>]
|
||||
Note that compared to other apps, this function currently lacks support
|
||||
for specifying a protocol schema (i.e. protocol://...).
|
||||
proxy: the proxy setting in the form: [scheme://][<username>:<password>@]<host>[:<port>]
|
||||
This currently supports http:// and https:// proxies.
|
||||
A hostname without scheme is assumed to be http.
|
||||
|
||||
reactor: reactor to be used to connect to the proxy
|
||||
|
||||
tls_options_factory: the TLS options to use when connecting through a https proxy
|
||||
|
||||
kwargs: other args to be passed to HostnameEndpoint
|
||||
|
||||
Returns:
|
||||
interfaces.IStreamClientEndpoint|None: endpoint to use to connect to the proxy,
|
||||
or None
|
||||
a tuple of
|
||||
endpoint to use to connect to the proxy, or None
|
||||
ProxyCredentials or if no credentials were found, or None
|
||||
|
||||
Raise:
|
||||
ValueError if proxy has no hostname or unsupported scheme.
|
||||
"""
|
||||
if proxy is None:
|
||||
return None
|
||||
return None, None
|
||||
|
||||
# Parse the connection string
|
||||
host, port = parse_host_port(proxy, default_port=1080)
|
||||
return HostnameEndpoint(reactor, host, port, **kwargs)
|
||||
# Note: urlsplit/urlparse cannot be used here as that does not work (for Python
|
||||
# 3.9+) on scheme-less proxies, e.g. host:port.
|
||||
scheme, host, port, credentials = parse_proxy(proxy)
|
||||
|
||||
proxy_endpoint = HostnameEndpoint(reactor, host, port, **kwargs)
|
||||
|
||||
if scheme == b"https":
|
||||
tls_options = tls_options_factory.creatorForNetloc(host, port)
|
||||
proxy_endpoint = wrapClientTLS(tls_options, proxy_endpoint)
|
||||
|
||||
return proxy_endpoint, credentials
|
||||
|
||||
|
||||
def parse_username_password(proxy: bytes) -> Tuple[Optional[ProxyCredentials], bytes]:
|
||||
def parse_proxy(
|
||||
proxy: bytes, default_scheme: bytes = b"http", default_port: int = 1080
|
||||
) -> Tuple[bytes, bytes, int, Optional[ProxyCredentials]]:
|
||||
"""
|
||||
Parses the username and password from a proxy declaration e.g
|
||||
username:password@hostname:port.
|
||||
Parse a proxy connection string.
|
||||
|
||||
Given a HTTP proxy URL, breaks it down into components and checks that it
|
||||
has a hostname (otherwise it is not useful to us when trying to find a
|
||||
proxy) and asserts that the URL has a scheme we support.
|
||||
|
||||
|
||||
Args:
|
||||
proxy: The proxy connection string.
|
||||
|
||||
Returns
|
||||
An instance of ProxyCredentials and the proxy connection string with any credentials
|
||||
stripped, i.e u:p@host:port -> host:port. If no credentials were found, the
|
||||
ProxyCredentials instance is replaced with None.
|
||||
"""
|
||||
if proxy and b"@" in proxy:
|
||||
# We use rsplit here as the password could contain an @ character
|
||||
credentials, proxy_without_credentials = proxy.rsplit(b"@", 1)
|
||||
return ProxyCredentials(credentials), proxy_without_credentials
|
||||
|
||||
return None, proxy
|
||||
|
||||
|
||||
def parse_host_port(hostport: bytes, default_port: int = None) -> Tuple[bytes, int]:
|
||||
"""
|
||||
Parse the hostname and port from a proxy connection byte string.
|
||||
|
||||
Args:
|
||||
hostport: The proxy connection string. Must be in the form 'host[:port]'.
|
||||
default_port: The default port to return if one is not found in `hostport`.
|
||||
proxy: The proxy connection string. Must be in the form '[scheme://][<username>:<password>@]host[:port]'.
|
||||
default_scheme: The default scheme to return if one is not found in `proxy`. Defaults to http
|
||||
default_port: The default port to return if one is not found in `proxy`. Defaults to 1080
|
||||
|
||||
Returns:
|
||||
A tuple containing the hostname and port. Uses `default_port` if one was not found.
|
||||
"""
|
||||
if b":" in hostport:
|
||||
host, port = hostport.rsplit(b":", 1)
|
||||
try:
|
||||
port = int(port)
|
||||
return host, port
|
||||
except ValueError:
|
||||
# the thing after the : wasn't a valid port; presumably this is an
|
||||
# IPv6 address.
|
||||
pass
|
||||
A tuple containing the scheme, hostname, port and ProxyCredentials.
|
||||
If no credentials were found, the ProxyCredentials instance is replaced with None.
|
||||
|
||||
return hostport, default_port
|
||||
Raise:
|
||||
ValueError if proxy has no hostname or unsupported scheme.
|
||||
"""
|
||||
# First check if we have a scheme present
|
||||
# Note: urlsplit/urlparse cannot be used (for Python # 3.9+) on scheme-less proxies, e.g. host:port.
|
||||
if b"://" not in proxy:
|
||||
proxy = b"".join([default_scheme, b"://", proxy])
|
||||
|
||||
url = urlparse(proxy)
|
||||
|
||||
if not url.hostname:
|
||||
raise ValueError("Proxy URL did not contain a hostname! Please specify one.")
|
||||
|
||||
if url.scheme not in (b"http", b"https"):
|
||||
raise ValueError(
|
||||
f"Unknown proxy scheme {url.scheme!s}; only 'http' and 'https' is supported."
|
||||
)
|
||||
|
||||
credentials = None
|
||||
if url.username and url.password:
|
||||
credentials = ProxyCredentials(b"".join([url.username, b":", url.password]))
|
||||
|
||||
return url.scheme, url.hostname, url.port or default_port, credentials
|
||||
|
|
|
|||
|
|
@ -14,47 +14,86 @@
|
|||
|
||||
""" This module contains base REST classes for constructing REST servlets. """
|
||||
import logging
|
||||
from typing import Dict, Iterable, List, Optional, overload
|
||||
from typing import Iterable, List, Mapping, Optional, Sequence, overload
|
||||
|
||||
from typing_extensions import Literal
|
||||
|
||||
from twisted.web.server import Request
|
||||
|
||||
from synapse.api.errors import Codes, SynapseError
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util import json_decoder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_integer(request, name, default=None, required=False):
|
||||
@overload
|
||||
def parse_integer(request: Request, name: str, default: int) -> int:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def parse_integer(request: Request, name: str, *, required: Literal[True]) -> int:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def parse_integer(
|
||||
request: Request, name: str, default: Optional[int] = None, required: bool = False
|
||||
) -> Optional[int]:
|
||||
...
|
||||
|
||||
|
||||
def parse_integer(
|
||||
request: Request, name: str, default: Optional[int] = None, required: bool = False
|
||||
) -> Optional[int]:
|
||||
"""Parse an integer parameter from the request string
|
||||
|
||||
Args:
|
||||
request: the twisted HTTP request.
|
||||
name (bytes/unicode): the name of the query parameter.
|
||||
default (int|None): value to use if the parameter is absent, defaults
|
||||
to None.
|
||||
required (bool): whether to raise a 400 SynapseError if the
|
||||
parameter is absent, defaults to False.
|
||||
name: the name of the query parameter.
|
||||
default: value to use if the parameter is absent, defaults to None.
|
||||
required: whether to raise a 400 SynapseError if the parameter is absent,
|
||||
defaults to False.
|
||||
|
||||
Returns:
|
||||
int|None: An int value or the default.
|
||||
An int value or the default.
|
||||
|
||||
Raises:
|
||||
SynapseError: if the parameter is absent and required, or if the
|
||||
parameter is present and not an integer.
|
||||
"""
|
||||
return parse_integer_from_args(request.args, name, default, required)
|
||||
args: Mapping[bytes, Sequence[bytes]] = request.args # type: ignore
|
||||
return parse_integer_from_args(args, name, default, required)
|
||||
|
||||
|
||||
def parse_integer_from_args(args, name, default=None, required=False):
|
||||
def parse_integer_from_args(
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[int] = None,
|
||||
required: bool = False,
|
||||
) -> Optional[int]:
|
||||
"""Parse an integer parameter from the request string
|
||||
|
||||
if not isinstance(name, bytes):
|
||||
name = name.encode("ascii")
|
||||
Args:
|
||||
args: A mapping of request args as bytes to a list of bytes (e.g. request.args).
|
||||
name: the name of the query parameter.
|
||||
default: value to use if the parameter is absent, defaults to None.
|
||||
required: whether to raise a 400 SynapseError if the parameter is absent,
|
||||
defaults to False.
|
||||
|
||||
if name in args:
|
||||
Returns:
|
||||
An int value or the default.
|
||||
|
||||
Raises:
|
||||
SynapseError: if the parameter is absent and required, or if the
|
||||
parameter is present and not an integer.
|
||||
"""
|
||||
name_bytes = name.encode("ascii")
|
||||
|
||||
if name_bytes in args:
|
||||
try:
|
||||
return int(args[name][0])
|
||||
return int(args[name_bytes][0])
|
||||
except Exception:
|
||||
message = "Query parameter %r must be an integer" % (name,)
|
||||
raise SynapseError(400, message, errcode=Codes.INVALID_PARAM)
|
||||
|
|
@ -66,36 +105,102 @@ def parse_integer_from_args(args, name, default=None, required=False):
|
|||
return default
|
||||
|
||||
|
||||
def parse_boolean(request, name, default=None, required=False):
|
||||
@overload
|
||||
def parse_boolean(request: Request, name: str, default: bool) -> bool:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def parse_boolean(request: Request, name: str, *, required: Literal[True]) -> bool:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def parse_boolean(
|
||||
request: Request, name: str, default: Optional[bool] = None, required: bool = False
|
||||
) -> Optional[bool]:
|
||||
...
|
||||
|
||||
|
||||
def parse_boolean(
|
||||
request: Request, name: str, default: Optional[bool] = None, required: bool = False
|
||||
) -> Optional[bool]:
|
||||
"""Parse a boolean parameter from the request query string
|
||||
|
||||
Args:
|
||||
request: the twisted HTTP request.
|
||||
name (bytes/unicode): the name of the query parameter.
|
||||
default (bool|None): value to use if the parameter is absent, defaults
|
||||
to None.
|
||||
required (bool): whether to raise a 400 SynapseError if the
|
||||
parameter is absent, defaults to False.
|
||||
name: the name of the query parameter.
|
||||
default: value to use if the parameter is absent, defaults to None.
|
||||
required: whether to raise a 400 SynapseError if the parameter is absent,
|
||||
defaults to False.
|
||||
|
||||
Returns:
|
||||
bool|None: A bool value or the default.
|
||||
A bool value or the default.
|
||||
|
||||
Raises:
|
||||
SynapseError: if the parameter is absent and required, or if the
|
||||
parameter is present and not one of "true" or "false".
|
||||
"""
|
||||
|
||||
return parse_boolean_from_args(request.args, name, default, required)
|
||||
args: Mapping[bytes, Sequence[bytes]] = request.args # type: ignore
|
||||
return parse_boolean_from_args(args, name, default, required)
|
||||
|
||||
|
||||
def parse_boolean_from_args(args, name, default=None, required=False):
|
||||
@overload
|
||||
def parse_boolean_from_args(
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: bool,
|
||||
) -> bool:
|
||||
...
|
||||
|
||||
if not isinstance(name, bytes):
|
||||
name = name.encode("ascii")
|
||||
|
||||
if name in args:
|
||||
@overload
|
||||
def parse_boolean_from_args(
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
*,
|
||||
required: Literal[True],
|
||||
) -> bool:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def parse_boolean_from_args(
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[bool] = None,
|
||||
required: bool = False,
|
||||
) -> Optional[bool]:
|
||||
...
|
||||
|
||||
|
||||
def parse_boolean_from_args(
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[bool] = None,
|
||||
required: bool = False,
|
||||
) -> Optional[bool]:
|
||||
"""Parse a boolean parameter from the request query string
|
||||
|
||||
Args:
|
||||
args: A mapping of request args as bytes to a list of bytes (e.g. request.args).
|
||||
name: the name of the query parameter.
|
||||
default: value to use if the parameter is absent, defaults to None.
|
||||
required: whether to raise a 400 SynapseError if the parameter is absent,
|
||||
defaults to False.
|
||||
|
||||
Returns:
|
||||
A bool value or the default.
|
||||
|
||||
Raises:
|
||||
SynapseError: if the parameter is absent and required, or if the
|
||||
parameter is present and not one of "true" or "false".
|
||||
"""
|
||||
name_bytes = name.encode("ascii")
|
||||
|
||||
if name_bytes in args:
|
||||
try:
|
||||
return {b"true": True, b"false": False}[args[name][0]]
|
||||
return {b"true": True, b"false": False}[args[name_bytes][0]]
|
||||
except Exception:
|
||||
message = (
|
||||
"Boolean query parameter %r must be one of ['true', 'false']"
|
||||
|
|
@ -111,7 +216,7 @@ def parse_boolean_from_args(args, name, default=None, required=False):
|
|||
|
||||
@overload
|
||||
def parse_bytes_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[bytes] = None,
|
||||
) -> Optional[bytes]:
|
||||
|
|
@ -120,7 +225,7 @@ def parse_bytes_from_args(
|
|||
|
||||
@overload
|
||||
def parse_bytes_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Literal[None] = None,
|
||||
*,
|
||||
|
|
@ -131,7 +236,7 @@ def parse_bytes_from_args(
|
|||
|
||||
@overload
|
||||
def parse_bytes_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[bytes] = None,
|
||||
required: bool = False,
|
||||
|
|
@ -140,7 +245,7 @@ def parse_bytes_from_args(
|
|||
|
||||
|
||||
def parse_bytes_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[bytes] = None,
|
||||
required: bool = False,
|
||||
|
|
@ -172,6 +277,42 @@ def parse_bytes_from_args(
|
|||
return default
|
||||
|
||||
|
||||
@overload
|
||||
def parse_string(
|
||||
request: Request,
|
||||
name: str,
|
||||
default: str,
|
||||
*,
|
||||
allowed_values: Optional[Iterable[str]] = None,
|
||||
encoding: str = "ascii",
|
||||
) -> str:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def parse_string(
|
||||
request: Request,
|
||||
name: str,
|
||||
*,
|
||||
required: Literal[True],
|
||||
allowed_values: Optional[Iterable[str]] = None,
|
||||
encoding: str = "ascii",
|
||||
) -> str:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def parse_string(
|
||||
request: Request,
|
||||
name: str,
|
||||
*,
|
||||
required: bool = False,
|
||||
allowed_values: Optional[Iterable[str]] = None,
|
||||
encoding: str = "ascii",
|
||||
) -> Optional[str]:
|
||||
...
|
||||
|
||||
|
||||
def parse_string(
|
||||
request: Request,
|
||||
name: str,
|
||||
|
|
@ -179,7 +320,7 @@ def parse_string(
|
|||
required: bool = False,
|
||||
allowed_values: Optional[Iterable[str]] = None,
|
||||
encoding: str = "ascii",
|
||||
):
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Parse a string parameter from the request query string.
|
||||
|
||||
|
|
@ -205,7 +346,7 @@ def parse_string(
|
|||
parameter is present, must be one of a list of allowed values and
|
||||
is not one of those allowed values.
|
||||
"""
|
||||
args: Dict[bytes, List[bytes]] = request.args # type: ignore
|
||||
args: Mapping[bytes, Sequence[bytes]] = request.args # type: ignore
|
||||
return parse_string_from_args(
|
||||
args,
|
||||
name,
|
||||
|
|
@ -239,9 +380,8 @@ def _parse_string_value(
|
|||
|
||||
@overload
|
||||
def parse_strings_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[List[str]] = None,
|
||||
*,
|
||||
allowed_values: Optional[Iterable[str]] = None,
|
||||
encoding: str = "ascii",
|
||||
|
|
@ -251,9 +391,20 @@ def parse_strings_from_args(
|
|||
|
||||
@overload
|
||||
def parse_strings_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: List[str],
|
||||
*,
|
||||
allowed_values: Optional[Iterable[str]] = None,
|
||||
encoding: str = "ascii",
|
||||
) -> List[str]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def parse_strings_from_args(
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[List[str]] = None,
|
||||
*,
|
||||
required: Literal[True],
|
||||
allowed_values: Optional[Iterable[str]] = None,
|
||||
|
|
@ -264,7 +415,7 @@ def parse_strings_from_args(
|
|||
|
||||
@overload
|
||||
def parse_strings_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[List[str]] = None,
|
||||
*,
|
||||
|
|
@ -276,7 +427,7 @@ def parse_strings_from_args(
|
|||
|
||||
|
||||
def parse_strings_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[List[str]] = None,
|
||||
required: bool = False,
|
||||
|
|
@ -325,7 +476,7 @@ def parse_strings_from_args(
|
|||
|
||||
@overload
|
||||
def parse_string_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[str] = None,
|
||||
*,
|
||||
|
|
@ -337,7 +488,7 @@ def parse_string_from_args(
|
|||
|
||||
@overload
|
||||
def parse_string_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[str] = None,
|
||||
*,
|
||||
|
|
@ -350,7 +501,7 @@ def parse_string_from_args(
|
|||
|
||||
@overload
|
||||
def parse_string_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[str] = None,
|
||||
required: bool = False,
|
||||
|
|
@ -361,7 +512,7 @@ def parse_string_from_args(
|
|||
|
||||
|
||||
def parse_string_from_args(
|
||||
args: Dict[bytes, List[bytes]],
|
||||
args: Mapping[bytes, Sequence[bytes]],
|
||||
name: str,
|
||||
default: Optional[str] = None,
|
||||
required: bool = False,
|
||||
|
|
@ -409,13 +560,14 @@ def parse_string_from_args(
|
|||
return strings[0]
|
||||
|
||||
|
||||
def parse_json_value_from_request(request, allow_empty_body=False):
|
||||
def parse_json_value_from_request(
|
||||
request: Request, allow_empty_body: bool = False
|
||||
) -> Optional[JsonDict]:
|
||||
"""Parse a JSON value from the body of a twisted HTTP request.
|
||||
|
||||
Args:
|
||||
request: the twisted HTTP request.
|
||||
allow_empty_body (bool): if True, an empty body will be accepted and
|
||||
turned into None
|
||||
allow_empty_body: if True, an empty body will be accepted and turned into None
|
||||
|
||||
Returns:
|
||||
The JSON value.
|
||||
|
|
@ -424,7 +576,7 @@ def parse_json_value_from_request(request, allow_empty_body=False):
|
|||
SynapseError if the request body couldn't be decoded as JSON.
|
||||
"""
|
||||
try:
|
||||
content_bytes = request.content.read()
|
||||
content_bytes = request.content.read() # type: ignore
|
||||
except Exception:
|
||||
raise SynapseError(400, "Error reading JSON content.")
|
||||
|
||||
|
|
@ -440,13 +592,15 @@ def parse_json_value_from_request(request, allow_empty_body=False):
|
|||
return content
|
||||
|
||||
|
||||
def parse_json_object_from_request(request, allow_empty_body=False):
|
||||
def parse_json_object_from_request(
|
||||
request: Request, allow_empty_body: bool = False
|
||||
) -> JsonDict:
|
||||
"""Parse a JSON object from the body of a twisted HTTP request.
|
||||
|
||||
Args:
|
||||
request: the twisted HTTP request.
|
||||
allow_empty_body (bool): if True, an empty body will be accepted and
|
||||
turned into an empty dict.
|
||||
allow_empty_body: if True, an empty body will be accepted and turned into
|
||||
an empty dict.
|
||||
|
||||
Raises:
|
||||
SynapseError if the request body couldn't be decoded as JSON or
|
||||
|
|
@ -457,14 +611,14 @@ def parse_json_object_from_request(request, allow_empty_body=False):
|
|||
if allow_empty_body and content is None:
|
||||
return {}
|
||||
|
||||
if type(content) != dict:
|
||||
if not isinstance(content, dict):
|
||||
message = "Content must be a JSON object."
|
||||
raise SynapseError(400, message, errcode=Codes.BAD_JSON)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def assert_params_in_dict(body, required):
|
||||
def assert_params_in_dict(body: JsonDict, required: Iterable[str]) -> None:
|
||||
absent = []
|
||||
for k in required:
|
||||
if k not in body:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue