import re from time import strftime, gmtime from string import ascii_letters HTTP_STATUS_CODES = { 100: "Continue", 101: "Switching Protocols", 102: "Processing", 200: "OK", 201: "Created", 202: "Accepted", 203: "Non Authoritative Information", 204: "No Content", 205: "Reset Content", 206: "Partial Content", 207: "Multi Status", 226: "IM Used", # see RFC 3229 300: "Multiple Choices", 301: "Moved Permanently", 302: "Found", 303: "See Other", 304: "Not Modified", 305: "Use Proxy", 307: "Temporary Redirect", 308: "Permanent Redirect", 400: "Bad Request", 401: "Unauthorized", 402: "Payment Required", # unused 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 406: "Not Acceptable", 407: "Proxy Authentication Required", 408: "Request Timeout", 409: "Conflict", 410: "Gone", 411: "Length Required", 412: "Precondition Failed", 413: "Request Entity Too Large", 414: "Request URI Too Long", 415: "Unsupported Media Type", 416: "Requested Range Not Satisfiable", 417: "Expectation Failed", 418: "I'm a teapot", # see RFC 2324 421: "Misdirected Request", # see RFC 7540 422: "Unprocessable Entity", 423: "Locked", 424: "Failed Dependency", 426: "Upgrade Required", 428: "Precondition Required", # see RFC 6585 429: "Too Many Requests", 431: "Request Header Fields Too Large", 449: "Retry With", # proprietary MS extension 451: "Unavailable For Legal Reasons", 500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout", 505: "HTTP Version Not Supported", 507: "Insufficient Storage", 510: "Not Extended", } HTTP_VERSION = 'HTTP/1.1' HTTP_METHODS = ['GET', 'POST', 'OPTIONS', 'HEAD', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT'] URL_REGEX_PATTERN = re.compile(r'/((.*?/?)+)?') FIRST_LINE_PATTERN = re.compile(rf'{"(" + "|".join(HTTP_METHODS) + ")"} {URL_REGEX_PATTERN.pattern} HTTP/1\.[01]') def get_status_code(code): return f'{HTTP_VERSION} {code} {HTTP_STATUS_CODES[code]}' SUCCESS = get_status_code(200) BAD_REQUEST = get_status_code(400) NOT_FOUND = get_status_code(404) METHOD_NOT_ALLOWED = get_status_code(405) def add_text_headers(status, html: str): """ Добавляет заголовки к ответной html-странице """ return '\r\n'.join([ status, f'Date: {strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime())}', 'Server: BrandNewServer', 'Content-Type: text/html; charset=utf-8', 'Connection: keep-alive', '', html ]) def validate_url(url): return bool(URL_REGEX_PATTERN.fullmatch(url)) def validate_first_line(line): return bool(FIRST_LINE_PATTERN.fullmatch(line)) def parse_cookies(cookies_line: str): cookies_line = cookies_line.strip().strip(';') pairs = cookies_line.split(';') d = {} for pair in pairs: try: key, value = pair.split('=', maxsplit=1) d[key] = value except ValueError: raise ValueError('Wrong format of cookies') return d def format_cookies(cookies: dict): """ Формирование cookie-строки из переданного словаря. """ pairs = [] for key, value in cookies: pairs.append(f'{key}={value}') return ';'.join(pairs) def parse_headers(request_line: str): request = request_line.split('\r\n') first_line = request.pop(0) if validate_first_line(first_line): method, url, http_ver = first_line.split() else: # Я не знаю зачем в условии необходимо завершать работу сервера, если пришел не-HTTP запрос, # поэтому оставлю эту строку здесь. # raise ValueError('Wrong format of HTTP request') raise ConnectionError('Wrong format of HTTP request') headers = {} for line in request: try: field, value = line.split(': ', maxsplit=1) except ValueError: # raise ValueError('Wrong format of HTTP header') raise ConnectionError('Wrong format of HTTP request') headers[field] = value return method, url, http_ver, headers def parse_query(query_line: str): pairs = query_line.strip().split('&') d = {} for pair in pairs: try: key, value = pair.split('=', maxsplit=1) d[url_decoder(key)] = url_decoder(value) except ValueError: # raise ValueError('Wrong format of query') raise ConnectionError('Wrong format of query') return d def url_decoder(url_line: str): url_line = url_line.replace('+', ' ') encoded = b'' i = 0 while i < len(url_line): if url_line[i] == '%': hex_value = url_line[i + 1: i + 3] encoded += bytes([int(hex_value, 16)]) i += 3 continue else: encoded += bytes([ord(url_line[i])]) i += 1 return encoded.decode() def url_encoder(line: str): s = '' for char in line: if char == ' ': s += '+' elif char not in ascii_letters: for byte in char.encode(): s += '%' + hex(byte)[2:].upper() else: s += char return s