Extract read*_request_body methods

This commit is contained in:
Unrud 2020-09-14 21:19:48 +02:00
parent 42ad18bc84
commit 0ce90d6b34
10 changed files with 63 additions and 51 deletions

View File

@ -99,30 +99,6 @@ class Application(
return request_environ return request_environ
def _decode(self, text, environ):
"""Try to magically decode ``text`` according to given ``environ``."""
# List of charsets to try
charsets = []
# First append content charset given in the request
content_type = environ.get("CONTENT_TYPE")
if content_type and "charset=" in content_type:
charsets.append(
content_type.split("charset=")[1].split(";")[0].strip())
# Then append default Radicale charset
charsets.append(self._encoding)
# Then append various fallbacks
charsets.append("utf-8")
charsets.append("iso8859-1")
# Try to decode
for charset in charsets:
try:
return text.decode(charset)
except UnicodeDecodeError:
pass
raise UnicodeDecodeError
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
with log.register_stream(environ["wsgi.errors"]): with log.register_stream(environ["wsgi.errors"]):
try: try:
@ -244,8 +220,9 @@ class Application(
login, password = login or "", password or "" login, password = login or "", password or ""
elif authorization.startswith("Basic"): elif authorization.startswith("Basic"):
authorization = authorization[len("Basic"):].strip() authorization = authorization[len("Basic"):].strip()
login, password = self._decode(base64.b64decode( login, password = httputils.decode_request(
authorization.encode("ascii")), environ).split(":", 1) self.configuration, environ, base64.b64decode(
authorization.encode("ascii"))).split(":", 1)
user = self._auth.login(login, password) or "" if login else "" user = self._auth.login(login, password) or "" if login else ""
if user and login == user: if user and login == user:
@ -317,22 +294,10 @@ class Application(
return response(status, headers, answer) return response(status, headers, answer)
def _read_raw_content(self, environ): def _read_xml_request_body(self, environ):
content_length = int(environ.get("CONTENT_LENGTH") or 0) content = httputils.decode_request(
if not content_length: self.configuration, environ,
return b"" httputils.read_raw_request_body(self.configuration, environ))
content = environ["wsgi.input"].read(content_length)
if len(content) < content_length:
raise RuntimeError("Request body too short: %d" % len(content))
return content
def _read_content(self, environ):
content = self._decode(self._read_raw_content(environ), environ)
logger.debug("Request content:\n%s", content)
return content
def _read_xml_content(self, environ):
content = self._decode(self._read_raw_content(environ), environ)
if not content: if not content:
return None return None
try: try:

View File

@ -33,7 +33,7 @@ class ApplicationMkcalendarMixin:
if "w" not in self._rights.authorization(user, path): if "w" not in self._rights.authorization(user, path):
return httputils.NOT_ALLOWED return httputils.NOT_ALLOWED
try: try:
xml_content = self._read_xml_content(environ) xml_content = self._read_xml_request_body(environ)
except RuntimeError as e: except RuntimeError as e:
logger.warning( logger.warning(
"Bad MKCALENDAR request on %r: %s", path, e, exc_info=True) "Bad MKCALENDAR request on %r: %s", path, e, exc_info=True)

View File

@ -34,7 +34,7 @@ class ApplicationMkcolMixin:
if not rights.intersect(permissions, "Ww"): if not rights.intersect(permissions, "Ww"):
return httputils.NOT_ALLOWED return httputils.NOT_ALLOWED
try: try:
xml_content = self._read_xml_content(environ) xml_content = self._read_xml_request_body(environ)
except RuntimeError as e: except RuntimeError as e:
logger.warning( logger.warning(
"Bad MKCOL request on %r: %s", path, e, exc_info=True) "Bad MKCOL request on %r: %s", path, e, exc_info=True)

View File

@ -347,7 +347,7 @@ class ApplicationPropfindMixin:
if not access.check("r"): if not access.check("r"):
return httputils.NOT_ALLOWED return httputils.NOT_ALLOWED
try: try:
xml_content = self._read_xml_content(environ) xml_content = self._read_xml_request_body(environ)
except RuntimeError as e: except RuntimeError as e:
logger.warning( logger.warning(
"Bad PROPFIND request on %r: %s", path, e, exc_info=True) "Bad PROPFIND request on %r: %s", path, e, exc_info=True)

View File

@ -91,7 +91,7 @@ class ApplicationProppatchMixin:
if not access.check("w"): if not access.check("w"):
return httputils.NOT_ALLOWED return httputils.NOT_ALLOWED
try: try:
xml_content = self._read_xml_content(environ) xml_content = self._read_xml_request_body(environ)
except RuntimeError as e: except RuntimeError as e:
logger.warning( logger.warning(
"Bad PROPPATCH request on %r: %s", path, e, exc_info=True) "Bad PROPPATCH request on %r: %s", path, e, exc_info=True)

View File

@ -118,7 +118,7 @@ class ApplicationPutMixin:
if not access.check("w"): if not access.check("w"):
return httputils.NOT_ALLOWED return httputils.NOT_ALLOWED
try: try:
content = self._read_content(environ) content = httputils.read_request_body(self.configuration, environ)
except RuntimeError as e: except RuntimeError as e:
logger.warning("Bad PUT request on %r: %s", path, e, exc_info=True) logger.warning("Bad PUT request on %r: %s", path, e, exc_info=True)
return httputils.BAD_REQUEST return httputils.BAD_REQUEST

View File

@ -262,7 +262,7 @@ class ApplicationReportMixin:
if not access.check("r"): if not access.check("r"):
return httputils.NOT_ALLOWED return httputils.NOT_ALLOWED
try: try:
xml_content = self._read_xml_content(environ) xml_content = self._read_xml_request_body(environ)
except RuntimeError as e: except RuntimeError as e:
logger.warning( logger.warning(
"Bad REPORT request on %r: %s", path, e, exc_info=True) "Bad REPORT request on %r: %s", path, e, exc_info=True)

View File

@ -24,6 +24,8 @@ Helper functions for HTTP.
from http import client from http import client
from radicale.log import logger
NOT_ALLOWED = ( NOT_ALLOWED = (
client.FORBIDDEN, (("Content-Type", "text/plain"),), client.FORBIDDEN, (("Content-Type", "text/plain"),),
"Access to the requested resource forbidden.") "Access to the requested resource forbidden.")
@ -61,3 +63,45 @@ INTERNAL_SERVER_ERROR = (
"A server error occurred. Please contact the administrator.") "A server error occurred. Please contact the administrator.")
DAV_HEADERS = "1, 2, 3, calendar-access, addressbook, extended-mkcol" DAV_HEADERS = "1, 2, 3, calendar-access, addressbook, extended-mkcol"
def decode_request(configuration, environ, text):
"""Try to magically decode ``text`` according to given ``environ``."""
# List of charsets to try
charsets = []
# First append content charset given in the request
content_type = environ.get("CONTENT_TYPE")
if content_type and "charset=" in content_type:
charsets.append(
content_type.split("charset=")[1].split(";")[0].strip())
# Then append default Radicale charset
charsets.append(configuration.get("encoding", "request"))
# Then append various fallbacks
charsets.append("utf-8")
charsets.append("iso8859-1")
# Try to decode
for charset in charsets:
try:
return text.decode(charset)
except UnicodeDecodeError:
pass
raise UnicodeDecodeError
def read_raw_request_body(configuration, environ):
content_length = int(environ.get("CONTENT_LENGTH") or 0)
if not content_length:
return b""
content = environ["wsgi.input"].read(content_length)
if len(content) < content_length:
raise RuntimeError("Request body too short: %d" % len(content))
return content
def read_request_body(configuration, environ):
content = decode_request(
configuration, environ, read_raw_request_body(configuration, environ))
logger.debug("Request content:\n%s", content)
return content

View File

@ -21,7 +21,7 @@ Custom web plugin.
from http import client from http import client
from radicale import web from radicale import httputils, web
class Web(web.BaseWeb): class Web(web.BaseWeb):
@ -29,5 +29,5 @@ class Web(web.BaseWeb):
return client.OK, {"Content-Type": "text/plain"}, "custom" return client.OK, {"Content-Type": "text/plain"}, "custom"
def post(self, environ, base_prefix, path, user): def post(self, environ, base_prefix, path, user):
answer = "echo:" + environ["wsgi.input"].read().decode() content = httputils.read_request_body(self.configuration, environ)
return client.OK, {"Content-Type": "text/plain"}, answer return client.OK, {"Content-Type": "text/plain"}, "echo:" + content

View File

@ -63,5 +63,8 @@ class BaseWeb:
``user`` is empty for anonymous users. ``user`` is empty for anonymous users.
Use ``httputils.read*_request_body(self.configuration, environ)`` to
read the body.
""" """
return httputils.METHOD_NOT_ALLOWED return httputils.METHOD_NOT_ALLOWED