Exemple #1
0
async def parse_client(reader, document):
    """parse a client document from reader"""

    # --- status: HTTP/1.1 <code> [<message>]
    status = await reader.readline()
    toks = status.split()

    if len(toks) == 1:
        raise HTTPException(400, "Bad Request", "malformed status line")

    if toks[0] != "HTTP/1.1":
        raise HTTPException(400, "Bad Request",
                            f"unsupported HTTP protocol: {toks[0]}")

    try:
        document.http_status_code = int(toks[1])
    except ValueError as exc:
        raise HTTPException(400, "Bad Request",
                            f"invalid status code: {toks[1]}") from exc

    if len(toks) == 2:
        document.http_status_message = ""
    else:
        document.http_status_message = " ".join(toks[2:])

    await parse_headers_and_body(reader, document)

    parse_content(document)
Exemple #2
0
async def parse_server(reader, document):
    """parse a server document from reader"""

    # --- status: <method> <resource> HTTP/1.1
    status = await reader.readline()
    toks = status.split()

    if len(toks) != 3:
        raise HTTPException(400, "Bad Request", "malformed status line")

    if toks[2] != "HTTP/1.1":
        raise HTTPException(400, "Bad Request",
                            f"unsupported HTTP protocol: {toks[2]}")

    document.http_method = toks[0].upper()
    res = urlparse.urlparse(toks[1])
    document.http_resource = res.path
    if res.query:
        document.http_query_string = res.query
        for key, val in urlparse.parse_qs(res.query).items():
            document.http_query[key] = val[0] if len(val) == 1 else val

    await parse_headers_and_body(reader, document)

    if document.http_method == "GET":
        document.content = document.http_query
    elif document.http_method in ("PATCH", "POST", "PUT"):
        parse_content(document)
Exemple #3
0
async def parse_headers_and_body(  # pylint: disable=too-many-branches
        reader, document):
    """parse headers and body from reader into document"""

    # --- headers
    while len(header := await reader.readline()) > 0:
        if len(document.http_headers) == reader.max_header_count:
            raise HTTPException(400, "Bad Request",
                                "max header count exceeded")
        test = header.split(":", 1)
        if len(test) != 2:
            raise HTTPException(400, "Bad Request", "header missing colon")
        name, value = test
        document.http_headers[name.strip().lower()] = value.strip()
Exemple #4
0
    async def readline(self):
        """read a line (ends in \n or \r\n) as ascii"""
        while True:
            test = self.buffer.split(b"\n", 1)
            if len(test) == 2:
                line, self.buffer = test
                if line.endswith(b"\r"):
                    line = line[:-1]
                if len(line) > self.max_line_length:
                    raise HTTPException(431, "Request Header Fields Too Long")
                return line.decode("ascii")

            if len(self.buffer) > self.max_line_length:
                raise HTTPException(431, "Request Header Fields Too Long",
                                    "no end of line encountered")
            await self.read_block()
Exemple #5
0
async def parse_http_content(reader, document):
    """parse the http body from reader"""

    if document.http_headers.get("transfer-encoding") == "chunked":
        return await parse_chunked(reader, document)

    length = document.http_headers.get("content-length")
    if length is None:
        length = 0

    try:
        document.http_content_length = int(length)
    except ValueError as exc:
        raise HTTPException(400, "Bad Request",
                            "invalid content-length") from exc

    if reader.max_content_length:
        if document.http_content_length > reader.max_content_length:
            raise HTTPException(413, "Request Entity Too Large")

    if document.http_content_length:
        document.http_content = await reader.read(document.http_content_length)
Exemple #6
0
def parse_content(document):
    """extract content based on http_content_type"""
    if document.http_content_type == "application/json":
        try:
            document.content = json.loads(document.http_content)
        except json.decoder.JSONDecodeError as exc:
            raise HTTPException(400, "Bad Request", "invalid json content") \
                from exc
    elif document.http_content_type == "application/x-www-form-urlencoded":
        query = urlparse.parse_qs(document.http_content.decode("ascii"),
                                  keep_blank_values=True)
        document.content = {
            n: v[0] if len(v) == 1 else v
            for n, v in query.items()
        }
    elif document.http_content_type == "text/plain":
        if document.http_content is None:
            document.content = ""
        else:
            document.content = document.http_content.decode()
    else:
        document.content = document.http_content
Exemple #7
0
        if len(test) != 2:
            raise HTTPException(400, "Bad Request", "header missing colon")
        name, value = test
        document.http_headers[name.strip().lower()] = value.strip()

    # --- keep alive
    keep_alive = document.http_headers.get("connection", "keep-alive")
    document.is_keep_alive = keep_alive == "keep-alive"

    # --- http content
    await parse_http_content(reader, document)

    # --- content type
    if "content-type" in document.http_headers and \
            document.http_headers.get("content-type") == "":
        raise HTTPException(400, "Bad Request", "invalid content-type header")

    content_type = document.http_headers.get("content-type")
    if content_type:

        # lenient content-type parser
        pattern = (
            r"\s*"  # optional leading spaces
            "(?P<type>.+?)"  # content type
            r"\s*/\s*"  # slash with optional spaces
            "(?P<subtype>[^;]+?)"  # content subtype
            "("  # start of optional parameter specification
            r"\s*;\s*"  # semicolon with optional spaces
            "(?P<attribute>.+?)"  # attribute name
            r"\s*=\s*"  # equal with optional spaces
            "(?P<value>.+?)"  # attribute value