import re from time import strftime, gmtime from string import ascii_letters HTTP_VERSION = 'HTTP/1.1' SUCCESS = f'{HTTP_VERSION} 200 OK' BAD_REQUEST = f'{HTTP_VERSION} 400 Bad Request' NOT_FOUND = f'{HTTP_VERSION} 404 Not Found' METHOD_NOT_ALLOWED = f'{HTTP_VERSION} Method Not Allowed' 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 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