Skip to content

Commit

Permalink
Extract read*_request_body methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Unrud committed Sep 14, 2020
1 parent 5aef41e commit 6f3a952
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 51 deletions.
49 changes: 7 additions & 42 deletions radicale/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,30 +99,6 @@ def _headers_log(self, 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):
with log.register_stream(environ["wsgi.errors"]):
try:
Expand Down Expand Up @@ -244,8 +220,9 @@ def response(status, headers=(), answer=None):
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:
Expand Down Expand Up @@ -317,22 +294,10 @@ def response(status, headers=(), answer=None):

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:
Expand Down
2 changes: 1 addition & 1 deletion radicale/app/mkcalendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def do_MKCALENDAR(self, environ, base_prefix, path, user):
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)
Expand Down
2 changes: 1 addition & 1 deletion radicale/app/mkcol.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def do_MKCOL(self, environ, base_prefix, path, user):
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)
Expand Down
2 changes: 1 addition & 1 deletion radicale/app/propfind.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def do_PROPFIND(self, environ, base_prefix, path, user):
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)
Expand Down
2 changes: 1 addition & 1 deletion radicale/app/proppatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def do_PROPPATCH(self, environ, base_prefix, path, user):
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)
Expand Down
2 changes: 1 addition & 1 deletion radicale/app/put.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def do_PUT(self, environ, base_prefix, path, user):
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
Expand Down
2 changes: 1 addition & 1 deletion radicale/app/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def do_REPORT(self, environ, base_prefix, path, user):
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)
Expand Down
44 changes: 44 additions & 0 deletions radicale/httputils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

from http import client

from radicale.log import logger

NOT_ALLOWED = (
client.FORBIDDEN, (("Content-Type", "text/plain"),),
"Access to the requested resource forbidden.")
Expand Down Expand Up @@ -61,3 +63,45 @@
"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
6 changes: 3 additions & 3 deletions radicale/tests/custom/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@

from http import client

from radicale import web
from radicale import httputils, web


class Web(web.BaseWeb):
def get(self, environ, base_prefix, path, user):
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 changes: 3 additions & 0 deletions radicale/web/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,8 @@ def post(self, environ, base_prefix, path, user):
``user`` is empty for anonymous users.
Use ``httputils.read*_request_body(self.configuration, environ)`` to
read the body.
"""
return httputils.METHOD_NOT_ALLOWED

0 comments on commit 6f3a952

Please sign in to comment.