from typing import Tuple


class Parser:
    """Parser for incoming HTTP data"""
    def __init__(self, config) -> None:
        self.state = ''
        self.headers = {}
        self.encoding = config.options.get("Server", "DefaultEncoding")
        self.current_length = 0

    def set_state(self, state: str) -> None:
        """Overwrite current state if it's either empty or the body is incomplete"""
        if self.state == '' or self.state == 'BODY_INCOMPLETE':
            self.state = state

    def set_encoding(self) -> None:
        """If the Content-Type header is set, try to get the encoding"""
        if "Content-Type" in self.headers:  # Check if the Content-Type header is present
            content_type = "".join(self.headers["Content-Type"].split()).split(";")  # Split Content-Type into an array
            for ctx in content_type:  # Walk through each parameter in Content-Type
                if "charset" in ctx:
                    self.encoding = ctx.split("=")[1].upper()  # Set the Encoding to the one specified in the Headers

    def verify_headers(self, req: any, headers_ok: bool) -> None:
        """Verify that the expected headers are present

        :param req: HTTP Request
        :param headers_ok: Boolean based on if headers were already verified
        """
        if not headers_ok:  # If headers were not already gathered
            try:
                headers = req.decode().split("\r\n", 1)[1].split("\r\n\r\n")[0].split("\r\n")  # Split the request
                # headers into an array
                for header in headers:
                    h = header.split(":")
                    self.headers[h[0]] = h[1].strip()  # Store the header and it's value
            except (UnicodeDecodeError, IndexError):
                self.set_state('NO_HEADERS')

    def check_for_body(self, req: any, headers_ok: bool) -> None:
        """If a body is present in the request, try to get it

        :param req: HTTP Request
        :param headers_ok: Boolean based on if headers were already verified
        """
        try:
            if not headers_ok:  # If we didn't already read the headers, split the request so we only get the body
                body = req.decode(self.encoding).split("\r\n", 1)[1].split("\r\n\r\n")[1]
            else:  # Read the full data as the body
                body = req.decode(self.encoding)

            if "Content-Length" in self.headers:  # If Content-Length is in the headers
                expected_length = int(self.headers["Content-Length"])  # Get the Expected length of the Body
                self.current_length += len(body.encode())  # Calculate how far we are into the body

                if expected_length != self.current_length:  # If there is missing data from the body
                    self.set_state('BODY_INCOMPLETE')
                else:  # Full body received
                    self.set_state('DONE')
            else:  # No Content-Length was present
                self.set_state('INVALID_CONTENT_LENGTH')
        except IndexError:  # Some of the body was in the request together with the headers and we are missing some
            self.set_state('BODY_INCOMPLETE')
        if self.state != 'BODY_INCOMPLETE':  # We didn't find a body in the request
            self.set_state('NO_BODY')

    def parse_request(self, req: any, headers_ok: bool = False) -> str:
        """Parse an HTTP Request

        :param req: HTTP Request
        :param headers_ok: Boolean based on if headers were already verified
        :return: state - NO_HEADERS, BODY_INCOMPLETE, DONE, INVALID_CONTENT_LENGTH, NO_BODY
        """
        # Verify Expected Headers
        self.verify_headers(req, headers_ok)
        # Set Encoding if header is present
        self.set_encoding()
        # Check if the request contains a body
        self.check_for_body(req, headers_ok)
        # Return current state
        return self.state

    async def parse_buffer(self, buffer) -> Tuple[list, str, bool]:
        # Point Buffer to the start
        buffer.seek(0)
        # Holds if we read all headers
        end_of_headers = False
        # Request Data
        data = []
        # Request Body
        body = ""

        # Get the full body plus headers
        for index, line in enumerate(buffer):
            if index == 0:
                data = line.decode(self.encoding).split()
            else:
                if not end_of_headers:
                    if line == b'\r\n':
                        end_of_headers = True
                else:
                    body += line.decode(self.encoding)
        return data, body, end_of_headers

from typing import Tuple


class Parser:
    """Parser for incoming HTTP data"""
    def __init__(self, config) -> None:
        self.state = ''
        self.headers = {}
        self.encoding = config.options.get("Server", "DefaultEncoding")
        self.current_length = 0

    def set_state(self, state: str) -> None:
        """Overwrite current state if it's either empty or the body is incomplete"""
        if self.state == '' or self.state == 'BODY_INCOMPLETE':
            self.state = state

    def set_encoding(self) -> None:
        """If the Content-Type header is set, try to get the encoding"""
        if "Content-Type" in self.headers:  # Check if the Content-Type header is present
            content_type = "".join(self.headers["Content-Type"].split()).split(";")  # Split Content-Type into an array
            for ctx in content_type:  # Walk through each parameter in Content-Type
                if "charset" in ctx:
                    self.encoding = ctx.split("=")[1].upper()  # Set the Encoding to the one specified in the Headers

    def verify_headers(self, req: any, headers_ok: bool) -> None:
        """Verify that the expected headers are present

        :param req: HTTP Request
        :param headers_ok: Boolean based on if headers were already verified
        """
        if not headers_ok:  # If headers were not already gathered
            try:
                headers = req.decode().split("\r\n", 1)[1].split("\r\n\r\n")[0].split("\r\n")  # Split the request
                # headers into an array
                for header in headers:
                    h = header.split(":")
                    self.headers[h[0]] = h[1].strip()  # Store the header and it's value
            except (UnicodeDecodeError, IndexError):
                self.set_state('NO_HEADERS')

    def check_for_body(self, req: any, headers_ok: bool) -> None:
        """If a body is present in the request, try to get it

        :param req: HTTP Request
        :param headers_ok: Boolean based on if headers were already verified
        """
        try:
            if not headers_ok:  # If we didn't already read the headers, split the request so we only get the body
                body = req.decode(self.encoding).split("\r\n", 1)[1].split("\r\n\r\n")[1]
            else:  # Read the full data as the body
                body = req.decode(self.encoding)

            if "Content-Length" in self.headers:  # If Content-Length is in the headers
                expected_length = int(self.headers["Content-Length"])  # Get the Expected length of the Body
                self.current_length += len(body.encode())  # Calculate how far we are into the body

                if expected_length != self.current_length:  # If there is missing data from the body
                    self.set_state('BODY_INCOMPLETE')
                else:  # Full body received
                    self.set_state('DONE')
            else:  # No Content-Length was present
                self.set_state('INVALID_CONTENT_LENGTH')
        except IndexError:  # Some of the body was in the request together with the headers and we are missing some
            self.set_state('BODY_INCOMPLETE')
        if self.state != 'BODY_INCOMPLETE':  # We didn't find a body in the request
            self.set_state('NO_BODY')

    def parse_request(self, req: any, headers_ok: bool = False) -> str:
        """Parse an HTTP Request

        :param req: HTTP Request
        :param headers_ok: Boolean based on if headers were already verified
        :return: state - NO_HEADERS, BODY_INCOMPLETE, DONE, INVALID_CONTENT_LENGTH, NO_BODY
        """
        # Verify Expected Headers
        self.verify_headers(req, headers_ok)
        # Set Encoding if header is present
        self.set_encoding()
        # Check if the request contains a body
        self.check_for_body(req, headers_ok)
        # Return current state
        return self.state

    async def parse_buffer(self, buffer) -> Tuple[list, str, bool]:
        # Point Buffer to the start
        buffer.seek(0)
        # Holds if we read all headers
        end_of_headers = False
        # Request Data
        data = []
        # Request Body
        body = ""

        # Get the full body plus headers
        for index, line in enumerate(buffer):
            if index == 0:
                data = line.decode(self.encoding).split()
            else:
                if not end_of_headers:
                    if line == b'\r\n':
                        end_of_headers = True
                else:
                    body += line.decode(self.encoding)
        return data, body, end_of_headers

Parser for incoming HTTP data Overwrite current state if it's either empty or the body is incomplete If the Content-Type header is set, try to get the encoding # Check if the Content-Type header is present # Split Content-Type into an array # Walk through each parameter in Content-Type # Set the Encoding to the one specified in the Headers Verify that the expected headers are present          :param req: HTTP Request         :param headers_ok: Boolean based on if headers were already verified # If headers were not already gathered # Split the request # headers into an array # Store the header and it's value If a body is present in the request, try to get it          :param req: HTTP Request         :param headers_ok: Boolean based on if headers were already verified # If we didn't already read the headers, split the request so we only get the body # Read the full data as the body # If Content-Length is in the headers # Get the Expected length of the Body # Calculate how far we are into the body # If there is missing data from the body # Full body received # No Content-Length was present # Some of the body was in the request together with the headers and we are missing some # We didn't find a body in the request Parse an HTTP Request          :param req: HTTP Request         :param headers_ok: Boolean based on if headers were already verified         :return: state - NO_HEADERS, BODY_INCOMPLETE, DONE, INVALID_CONTENT_LENGTH, NO_BODY # Verify Expected Headers # Set Encoding if header is present # Check if the request contains a body # Return current state # Point Buffer to the start # Holds if we read all headers # Request Data # Request Body # Get the full body plus headers