1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-06-26 16:45:52 +00:00
Radicale/radicale/httputils.py

123 lines
4.8 KiB
Python
Raw Normal View History

2021-12-08 21:45:42 +01:00
# This file is part of Radicale - CalDAV and CardDAV server
2018-08-28 16:19:36 +02:00
# Copyright © 2008 Nicolas Kandel
# Copyright © 2008 Pascal Halter
# Copyright © 2008-2017 Guillaume Ayoub
2019-06-17 04:13:24 +02:00
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
2018-08-28 16:19:36 +02:00
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
2020-01-12 23:32:28 +01:00
"""
Helper functions for HTTP.
"""
2021-07-26 20:56:46 +02:00
import contextlib
2018-08-28 16:19:36 +02:00
from http import client
2021-07-26 20:56:46 +02:00
from typing import List, cast
2018-08-28 16:19:36 +02:00
2021-07-26 20:56:46 +02:00
from radicale import config, types
2020-09-14 21:19:48 +02:00
from radicale.log import logger
2021-07-26 20:56:46 +02:00
NOT_ALLOWED: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.FORBIDDEN, (("Content-Type", "text/plain"),),
"Access to the requested resource forbidden.")
2021-07-26 20:56:46 +02:00
FORBIDDEN: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.FORBIDDEN, (("Content-Type", "text/plain"),),
"Action on the requested resource refused.")
2021-07-26 20:56:46 +02:00
BAD_REQUEST: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.BAD_REQUEST, (("Content-Type", "text/plain"),), "Bad Request")
2021-07-26 20:56:46 +02:00
NOT_FOUND: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.NOT_FOUND, (("Content-Type", "text/plain"),),
"The requested resource could not be found.")
2021-07-26 20:56:46 +02:00
CONFLICT: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.CONFLICT, (("Content-Type", "text/plain"),),
"Conflict in the request.")
2021-07-26 20:56:46 +02:00
METHOD_NOT_ALLOWED: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.METHOD_NOT_ALLOWED, (("Content-Type", "text/plain"),),
"The method is not allowed on the requested resource.")
2021-07-26 20:56:46 +02:00
PRECONDITION_FAILED: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.PRECONDITION_FAILED,
(("Content-Type", "text/plain"),), "Precondition failed.")
2021-07-26 20:56:46 +02:00
REQUEST_TIMEOUT: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.REQUEST_TIMEOUT, (("Content-Type", "text/plain"),),
"Connection timed out.")
2021-07-26 20:56:46 +02:00
REQUEST_ENTITY_TOO_LARGE: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.REQUEST_ENTITY_TOO_LARGE, (("Content-Type", "text/plain"),),
"Request body too large.")
2021-07-26 20:56:46 +02:00
REMOTE_DESTINATION: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.BAD_GATEWAY, (("Content-Type", "text/plain"),),
"Remote destination not supported.")
2021-07-26 20:56:46 +02:00
DIRECTORY_LISTING: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.FORBIDDEN, (("Content-Type", "text/plain"),),
"Directory listings are not supported.")
2021-07-26 20:56:46 +02:00
INTERNAL_SERVER_ERROR: types.WSGIResponse = (
2018-08-28 16:19:36 +02:00
client.INTERNAL_SERVER_ERROR, (("Content-Type", "text/plain"),),
"A server error occurred. Please contact the administrator.")
2021-07-26 20:56:46 +02:00
DAV_HEADERS: str = "1, 2, 3, calendar-access, addressbook, extended-mkcol"
2020-09-14 21:19:48 +02:00
2021-07-26 20:56:46 +02:00
def decode_request(configuration: "config.Configuration",
environ: types.WSGIEnviron, text: bytes) -> str:
2020-09-14 21:19:48 +02:00
"""Try to magically decode ``text`` according to given ``environ``."""
# List of charsets to try
2021-07-26 20:56:46 +02:00
charsets: List[str] = []
2020-09-14 21:19:48 +02:00
# 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
2021-07-26 20:56:46 +02:00
charsets.append(cast(str, configuration.get("encoding", "request")))
2020-09-14 21:19:48 +02:00
# Then append various fallbacks
charsets.append("utf-8")
charsets.append("iso8859-1")
# Remove duplicates
for i, s in reversed(list(enumerate(charsets))):
if s in charsets[:i]:
del charsets[i]
2020-09-14 21:19:48 +02:00
# Try to decode
for charset in charsets:
2021-07-26 20:56:46 +02:00
with contextlib.suppress(UnicodeDecodeError):
2020-09-14 21:19:48 +02:00
return text.decode(charset)
raise UnicodeDecodeError("decode_request", text, 0, len(text),
"all codecs failed [%s]" % ", ".join(charsets))
2020-09-14 21:19:48 +02:00
2021-07-26 20:56:46 +02:00
def read_raw_request_body(configuration: "config.Configuration",
environ: types.WSGIEnviron) -> bytes:
2020-09-14 21:19:48 +02:00
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
2021-07-26 20:56:46 +02:00
def read_request_body(configuration: "config.Configuration",
environ: types.WSGIEnviron) -> str:
content = decode_request(configuration, environ,
read_raw_request_body(configuration, environ))
2020-09-14 21:19:48 +02:00
logger.debug("Request content:\n%s", content)
return content
2022-01-18 18:20:15 +01:00
def redirect(location: str, status: int = client.FOUND) -> types.WSGIResponse:
return (status,
{"Location": location, "Content-Type": "text/plain"},
"Redirected to %s" % location)