HTTP_BAD_REQUEST = 400 HTTP_CREATED = 201 HTTP_NO_CONTENT = 204 HTTP_NOT_MODIFIED = 304 HTTP_OK = 200 HTTP_PRECONDITION_FAILED = 412 class DAVError(Exception):
    """General error class that is used to signal HTTP and WEBDAV errors.

    May contain a :class:`DAVErrorCondition`.
    """

    # TODO: Ian Bicking proposed to add an additional 'comment' arg, but
    #       couldn't we use the existing 'context_info'?
    # @@: This should also take some message value, for a detailed error message.
    #     This would be helpful for debugging.

    def __init__(
        self, status_code, context_info=None, *, src_exception=None, err_condition=None
    ):
        # allow passing of Pre- and Postconditions, see
        # http://www.webdav.org/specs/rfc4918.html#precondition.postcondition.xml.elements
        self.value = int(status_code)
        self.context_info = context_info
        self.src_exception = src_exception
        self.err_condition = err_condition
        if util.is_str(err_condition):
            self.err_condition = DAVErrorCondition(err_condition)
        assert (
            self.err_condition is None or type(self.err_condition) is DAVErrorCondition
        )

    def __repr__(self):
        return "DAVError({})".format(self.get_user_info())

    def get_user_info(self):
        """Return readable string."""
        if self.value in ERROR_DESCRIPTIONS:
            s = "{}".format(ERROR_DESCRIPTIONS[self.value])
        else:
            s = "{}".format(self.value)

        if self.context_info:
            s += ": {}".format(self.context_info)
        elif self.value in ERROR_RESPONSES:
            s += ": {}".format(ERROR_RESPONSES[self.value])

        if self.src_exception:
            s += "\n    Source exception: '{}'".format(self.src_exception)

        if self.err_condition:
            s += "\n    Error condition: '{}'".format(self.err_condition)
        return s

    def get_response_page(self):
        """Return a tuple (content-type, response page)."""
        # If it has pre- or post-condition: return as XML response
        if self.err_condition:
            return ("application/xml", util.to_bytes(self.err_condition.as_string()))

        # Else return as HTML
        status = get_http_status_string(self)
        html = []
        html.append(
            "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' "
            "'http://www.w3.org/TR/html4/strict.dtd'>"
        )
        html.append("<html><head>")
        html.append(
            "  <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>"
        )
        html.append("  <title>{}</title>".format(status))
        html.append("</head><body>")
        html.append("  <h1>{}</h1>".format(status))
        html.append("  <p>{}</p>".format(html_escape(self.get_user_info())))
        html.append("<hr/>")
        html.append(
            "<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/{}</a> - {}".format(
                __version__, html_escape(str(datetime.datetime.now()), "utf-8")
            )
        )
        html.append("</body></html>")
        html = "\n".join(html)
        return ("text/html", util.to_bytes(html)) def as_DAVError(e):
    """Convert any non-DAVError exception to HTTP_INTERNAL_ERROR."""
    if isinstance(e, DAVError):
        return e
    elif isinstance(e, Exception):
        # traceback.print_exc()
        return DAVError(HTTP_INTERNAL_ERROR, src_exception=e)
    else:
        return DAVError(HTTP_INTERNAL_ERROR, "{}".format(e)) def get_http_status_string(v):
    """Return HTTP response string, e.g. 204 -> ('204 No Content').
    The return string always includes descriptive text, to satisfy Apache mod_dav.

    `v`: status code or DAVError
    """
    code = get_http_status_code(v)
    try:
        return ERROR_DESCRIPTIONS[code]
    except KeyError:
        return "{} Status".format(code) def is_etree_element(obj):
def string_to_xml(text):
def xml_to_bytes(element, *, pretty=False):
def make_multistatus_el():
def make_prop_elem():
def make_sub_element(parent, tag, *, nsmap=None):
def element_content_as_string(element):
import base64
import calendar
import collections.abc
import logging
import mimetypes
import os
import re
import socket
import stat
import sys
import time
    import importlib
from copy import deepcopy
from email.utils import formatdate, parsedate
from hashlib import md5
from pprint import pformat
from typing import Iterable, Optional
from urllib.parse import quote
from wsgidav.dav_error import (
    HTTP_BAD_REQUEST,
    HTTP_CREATED,
    HTTP_NO_CONTENT,
    HTTP_NOT_MODIFIED,
    HTTP_OK,
    HTTP_PRECONDITION_FAILED,
    DAVError,
    as_DAVError,
    get_http_status_string,
)
from wsgidav.xml_tools import etree, is_etree_element, make_sub_element, xml_to_bytes
    from wsgidav.default_conf import DEFAULT_LOGGER_DATE_FORMAT, DEFAULT_LOGGER_FORMAT
    for value in new_dict.values():
        target.append(value)

    return  # in-place does not return a value


# ========================================================================
# XML
# ========================================================================


def parse_xml_body(environ, *, allow_empty=False):
    """Read request body XML into an etree.Element.

    Return None, if no request body was sent.
    Raise HTTP_BAD_REQUEST, if something else went wrong.

    TODO: this is a very relaxed interpretation: should we raise HTTP_BAD_REQUEST
    instead, if CONTENT_LENGTH is missing, invalid, or 0?

    RFC: For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing
    a message-body MUST include a valid Content-Length header field unless the
    server is known to be HTTP/1.1 compliant.
    If a request contains a message-body and a Content-Length is not given, the
    server SHOULD respond with 400 (bad request) if it cannot determine the
    length of the message, or with 411 (length required) if it wishes to insist
    on receiving a valid Content-Length."

    So I'd say, we should accept a missing CONTENT_LENGTH, and try to read the
    content anyway.
    But WSGI doesn't guarantee to support input.read() without length(?).
    At least it locked, when I tried it with a request that had a missing
    content-type and no body.

    Current approach: if CONTENT_LENGTH is

    - valid and >0:
      read body (exactly <CONTENT_LENGTH> bytes) and parse the result.
    - 0:
      Assume empty body and return None or raise exception.
    - invalid (negative or not a number:
      raise HTTP_BAD_REQUEST
    - missing:
      NOT: Try to read body until end and parse the result.
      BUT: assume '0'
    - empty string:
      WSGI allows it to be empty or absent: treated like 'missing'.
    """
    #
    clHeader = environ.get("CONTENT_LENGTH", "").strip()
    #    content_length = -1 # read all of stream
    if clHeader == "":
        # No Content-Length given: read to end of stream
        # TODO: etree.parse() locks, if input is invalid?
        #        pfroot = etree.parse(environ["wsgi.input"]).getroot()
        # requestbody = environ["wsgi.input"].read()  # TODO: read() should be
        # called in a loop?
        requestbody = ""
    else:
        try:
            content_length = int(clHeader)
            if content_length < 0:
                raise DAVError(HTTP_BAD_REQUEST, "Negative content-length.")
        except ValueError:
            raise DAVError(HTTP_BAD_REQUEST, "content-length is not numeric.")

        if content_length == 0:
            requestbody = ""
        else:
            requestbody = environ["wsgi.input"].read(content_length)
            environ["wsgidav.all_input_read"] = 1

    if requestbody == "":
        if allow_empty:
            return None
        else:
            raise DAVError(HTTP_BAD_REQUEST, "Body must not be empty.")

    try:
        rootEL = etree.fromstring(requestbody)
    except Exception as e:
        raise DAVError(HTTP_BAD_REQUEST, "Invalid XML format.", src_exception=e)

    # If dumps of the body are desired, then this is the place to do it pretty:
    if environ.get("wsgidav.dump_request_body"):
        _logger.info(
            "{} XML request body:\n{}".format(
                environ["REQUEST_METHOD"],
                to_str(xml_to_bytes(rootEL, pretty=True)),
            )
        )
        environ["wsgidav.dump_request_body"] = False

    return rootEL


# def sendResponse(environ, start_response, body, content_type):
#    """Send a WSGI response for a HTML or XML string."""
#    assert content_type in ("application/xml", "text/html")
#
#    start_response(status, [("Content-Type", content_type),
#                            ("Date", get_rfc1123_time()),
#                            ("Content-Length", str(len(body))),
#                            ])
#    return [ body ]


def send_status_response(
    environ, start_response, e, *, add_headers=None, is_head=False
):
    """Start a WSGI response for a DAVError or status code."""
    status = get_http_status_string(e)
    headers = []
    if add_headers:
        headers.extend(add_headers)
    #    if 'keep-alive' in environ.get('HTTP_CONNECTION', '').lower():
    #        headers += [
    #            ('Connection', 'keep-alive'),
    #        ]


    if e in (HTTP_NOT_MODIFIED, HTTP_NO_CONTENT):