Просмотр исходного кода

Extract read*_request_body methods

Unrud 5 лет назад
Родитель
Сommit
0ce90d6b34

+ 7 - 42
radicale/app/__init__.py

@@ -99,30 +99,6 @@ class Application(
 
         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):
         with log.register_stream(environ["wsgi.errors"]):
             try:
@@ -244,8 +220,9 @@ class Application(
             login, password = login or "", password or ""
         elif authorization.startswith("Basic"):
             authorization = authorization[len("Basic"):].strip()
-            login, password = self._decode(base64.b64decode(
-                authorization.encode("ascii")), environ).split(":", 1)
+            login, password = httputils.decode_request(
+                self.configuration, environ, base64.b64decode(
+                    authorization.encode("ascii"))).split(":", 1)
 
         user = self._auth.login(login, password) or "" if login else ""
         if user and login == user:
@@ -317,22 +294,10 @@ class Application(
 
         return response(status, headers, answer)
 
-    def _read_raw_content(self, 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_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)
+    def _read_xml_request_body(self, environ):
+        content = httputils.decode_request(
+            self.configuration, environ,
+            httputils.read_raw_request_body(self.configuration, environ))
         if not content:
             return None
         try:

+ 1 - 1
radicale/app/mkcalendar.py

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

+ 1 - 1
radicale/app/mkcol.py

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

+ 1 - 1
radicale/app/propfind.py

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

+ 1 - 1
radicale/app/proppatch.py

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

+ 1 - 1
radicale/app/put.py

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

+ 1 - 1
radicale/app/report.py

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

+ 44 - 0
radicale/httputils.py

@@ -24,6 +24,8 @@ Helper functions for HTTP.
 
 from http import client
 
+from radicale.log import logger
+
 NOT_ALLOWED = (
     client.FORBIDDEN, (("Content-Type", "text/plain"),),
     "Access to the requested resource forbidden.")
@@ -61,3 +63,45 @@ INTERNAL_SERVER_ERROR = (
     "A server error occurred.  Please contact the administrator.")
 
 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

+ 3 - 3
radicale/tests/custom/web.py

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

+ 3 - 0
radicale/web/__init__.py

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