Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """ //////////////////////////////////////////////////
- HTTP PROXY IMPLEMENTATION
- ////////////////////////////////////////////////// """
- """ ////////////////////
- NOTES
- //////////////////// """
- """ Build Using Pycharm """
- """ ///// CODE SKELETON IS PROVIDED BY COMPUTER NETWORKS COURSE TEACHING ASSISTANTS ///// """
- """ TOTAL USED FUNCTIONS = ... """
- """ TOTAL UNUSED FUNCTIONS = ... """
- """ ///////////////////////////////////////////////////////////////////////////////////////////// """
- """ ///////////////////////////////////////////////////////////////////////////////////////////// """
- """ IMPORTS & GLOBAL VARIABLES """
- import sys
- import os
- import enum
- import re
- client_addr = ("127.0.0.1", 9877)
- class HttpRequestInfo(object):
- """
- Represents a HTTP request information
- Since you'll need to standardize all requests you get
- as specified by the document, after you parse the
- request from the TCP packet put the information you
- get in this object.
- To send the request to the remote server, call to_http_string
- on this object, convert that string to bytes then send it in
- the socket.
- client_address_info: address of the client;
- the client of the proxy, which sent the HTTP request.
- requested_host: the requested website, the remote website
- we want to visit.
- requested_port: port of the webserver we want to visit.
- requested_path: path of the requested resource, without
- including the website name.
- NOTE: you need to implement to_http_string() for this class.
- """
- def __init__(self, client_info, method: str, requested_host: str,
- requested_port: int,
- requested_path: str,
- headers: list):
- self.method = method
- self.client_address_info = client_info
- self.requested_host = requested_host
- self.requested_port = requested_port
- self.requested_path = requested_path
- # Headers will be represented as a list of lists
- # for example ["Host", "www.google.com"]
- # if you get a header as:
- # "Host: www.google.com:80"
- # convert it to ["Host", "www.google.com"] note that the
- # port is removed (because it goes into the request_port variable)
- self.headers = headers
- def to_http_string(self):
- """
- Convert the HTTP request/response
- to a valid HTTP string.
- As the protocol specifies:
- [request_line]\r\n
- [header]\r\n
- [headers..]\r\n
- \r\n
- You still need to convert this string
- to byte array before sending it to the socket,
- keeping it as a string in this stage is to ease
- debugging and testing.
- """
- HttpString = '';
- HttpString += self.method + ' '
- if self.requested_path == '':
- HttpString += self.requested_path
- else:
- HttpString += self.requested_path
- HttpString += ' ' + 'HTTP/1.0' + '\r\n'
- for item in self.headers:
- HttpString += item[0] + ': ' + item[1] + '\r\n'
- HttpString += '\r\n'
- return HttpString
- def to_byte_array(self, http_string):
- """
- Converts an HTTP string to a byte array.
- """
- return bytes(http_string, "UTF-8")
- def display(self):
- print(f"Client:", self.client_address_info)
- print(f"Method:", self.method)
- print(f"Host:", self.requested_host)
- print(f"Port:", self.requested_port)
- stringified = [": ".join([k, v]) for (k, v) in self.headers]
- print("Headers:\n", "\n".join(stringified))
- class HttpErrorResponse(object):
- """
- Represents a proxy-error-response.
- """
- def __init__(self, code, message):
- self.code = code
- self.message = message
- def to_http_string(self):
- """ Same as above """
- pass
- def to_byte_array(self, http_string):
- """
- Converts an HTTP string to a byte array.
- """
- return bytes(http_string, "UTF-8")
- def display(self):
- print(self.to_http_string())
- class HttpRequestState(enum.Enum):
- """
- The values here have nothing to do with
- response values i.e. 400, 502, ..etc.
- Leave this as is, feel free to add yours.
- """
- INVALID_INPUT = 0
- NOT_SUPPORTED = 1
- GOOD = 2
- PLACEHOLDER = -1
- def entry_point(proxy_port_number):
- """
- Entry point, start your code here.
- Please don't delete this function,
- but feel free to modify the code
- inside it.
- """
- setup_sockets(proxy_port_number)
- print("*" * 50)
- print("[entry_point] Implement me!")
- print("*" * 50)
- return None
- def setup_sockets(proxy_port_number):
- """
- Socket logic MUST NOT be written in the any
- class. Classes know nothing about the sockets.
- But feel free to add your own classes/functions.
- Feel free to delete this function.
- """
- print("Starting HTTP proxy on port:", proxy_port_number)
- # when calling socket.listen() pass a number
- # that's larger than 10 to avoid rejecting
- # connections automatically.
- print("*" * 50)
- print("[setup_sockets] Implement me!")
- print("*" * 50)
- return None
- def do_socket_logic():
- """
- Example function for some helper logic, in case you
- want to be tidy and avoid stuffing the main function.
- Feel free to delete this function.
- """
- pass
- def http_request_pipeline(source_addr, http_raw_data):
- """
- HTTP request processing pipeline.
- - Validates the given HTTP request and returns
- an error if an invalid request was given.
- - Parses it
- - Returns a sanitized HttpRequestInfo
- returns:
- HttpRequestInfo if the request was parsed correctly.
- HttpErrorResponse if the request was invalid.
- Please don't remove this function, but feel
- free to change its content
- """
- # Parse HTTP request
- validity = check_http_request_validity(http_raw_data)
- # Return error if needed, then:
- # parse_http_request()
- # sanitize_http_request()
- # Validate, sanitize, return Http object.
- print("*" * 50)
- print("[http_request_pipeline] Implement me!")
- print("*" * 50)
- return None
- def parse_http_request(source_addr, http_raw_data):
- print("*" * 30)
- print("Parsing Your Input")
- print("*" * 30)
- http_raw_data = http_raw_data.replace('\r\n\r\n', '')
- In = http_raw_data.split('\r\n')
- # if len(In) == 1:
- # In = http_raw_data.split('\r\n')
- Method = In[0].split(' ')[0]
- Path = In[0].split(' ')[1]
- HTTP_Version = In[0].split(' ')[2]
- if Path == "/" and In[1] == '':
- print("Invalid Input")
- elif Path[0] == "/" and In[1] == '':
- hostname = In[0].split(' ')[0]
- header_value = In[0].split(' ')[1]
- print("Relative Path")
- print("Method: " + Method)
- print("Path: " + Path)
- print("HTTP Version: " + HTTP_Version)
- print("Header: " + hostname.replace(':', ''))
- print("Header Name: " + header_value)
- HeadersCustom = check_extra_header(http_raw_data)
- HeadersCustom.append([hostname.replace(':', ''), header_value])
- ret = HttpRequestInfo(source_addr, Method, header_value, 80, Path, HeadersCustom)
- return ret
- pass
- elif Path[0] == "/" and In[1] != '':
- hostname = In[1].split(' ')[0]
- header_value = In[1].split(' ')[1]
- print("Relative Path")
- print("Method: " + Method)
- print("Path: " + Path)
- print("HTTP Version: " + HTTP_Version)
- print("Header: " + hostname.replace(':', ''))
- print("Header Name: " + header_value)
- HeadersCustom = check_extra_header(http_raw_data)
- HeadersCustom.append([hostname.replace(':', ''), header_value])
- ret = HttpRequestInfo(source_addr, Method, header_value, 80, Path, HeadersCustom)
- return ret
- pass
- else:
- print("Normal Path")
- print("Method: " + Method)
- print("Path: " + Path)
- print("HTTP Version: " + HTTP_Version)
- HeadersCustom = check_extra_header(http_raw_data)
- HeadersCustom.append(['Host:', Path])
- ret = HttpRequestInfo(source_addr, Method, Path, 80, Path, HeadersCustom)
- return ret
- pass
- def check_http_request_validity(http_raw_data) -> HttpRequestState:
- print("*" * 30)
- print("Validating Your Input")
- print("*" * 30)
- http_raw_data = http_raw_data.replace('\r\n\r\n', '')
- command = http_raw_data.split('\r\n')
- z = (len(command))
- # if len(command) == 1:
- # command = http_raw_data.split('\r\n')
- print(z)
- rex = re.compile("[a-z]{3} [\a-z]{1,} [\a-z]{3,}")
- rex2 = re.compile("[\a-z]{4,}: [\a-z]{3,}")
- meth = command[0].split(' ')[0]
- host_test = command[0].split(' ')[1]
- if rex.match(command[0].lower()) and method_checker(meth) == 1 and z < 2:
- print("Valid format")
- x = command[0].split(' ')[0]
- y = command[0].split(' ')[1]
- if y == "/":
- print("Invalid Input")
- return HttpRequestState.INVALID_INPUT
- else:
- parse_http_request(client_addr, http_raw_data)
- return HttpRequestState.GOOD
- pass
- elif rex.match(command[0].lower()) and method_checker(meth) == 1:
- print("Valid format")
- parse_http_request(client_addr, http_raw_data)
- return HttpRequestState.GOOD
- pass
- elif rex.match(command[0].lower()) and rex2.match(command[1].lower()) and method_checker(meth) == 1:
- print("Valid format")
- parse_http_request(client_addr, http_raw_data)
- return HttpRequestState.GOOD
- pass
- else:
- print("Invalid format")
- if len(command) == 1 and host_test.startswith('/'):
- return HttpRequestState.INVALID_INPUT
- elif method_checker(meth) == 0 and command[1].find(":") == -1:
- return HttpRequestState.INVALID_INPUT
- elif method_checker(meth) == 0 and command[0].find("/1.0") == -1:
- return HttpRequestState.INVALID_INPUT
- if method_checker(meth) == 0:
- return HttpRequestState.NOT_SUPPORTED
- elif method_checker(meth) == 2:
- return HttpRequestState.INVALID_INPUT
- pass
- pass
- def sanitize_http_request(request_info: HttpRequestInfo):
- """
- Puts an HTTP request on the sanitized (standard form)
- returns:
- A modified object of the HttpRequestInfo with
- sanitized fields
- for example, expand a URL to relative path + Host header.
- """
- print("*" * 50)
- print("[sanitize_http_request] Implement me!")
- print("*" * 50)
- ret = HttpRequestInfo(None, None, None, None, None, None)
- return ret
- #######################################
- # Leave the code below as is.
- #######################################
- def get_arg(param_index, default=None):
- """
- Gets a command line argument by index (note: index starts from 1)
- If the argument is not supplies, it tries to use a default value.
- If a default value isn't supplied, an error message is printed
- and terminates the program.
- """
- try:
- return sys.argv[param_index]
- except IndexError as e:
- if default:
- return default
- else:
- print(e)
- print(
- f"[FATAL] The comand-line argument #[{param_index}] is missing")
- exit(-1) # Program execution failed.
- def check_file_name():
- """
- Checks if this file has a valid name for *submission*
- leave this function and as and don't use it. it's just
- to notify you if you're submitting a file with a correct
- name.
- """
- script_name = os.path.basename(__file__)
- import re
- matches = re.findall(r"(\d{4}_){,2}lab2\.py", script_name)
- if not matches:
- print(f"[WARN] File name is invalid [{script_name}]")
- else:
- print(f"[LOG] File name is correct.")
- def check_extra_header(command):
- command = command.replace('\r\n\r\n','')
- Part = command.split('\r\n')
- length = len(Part)
- HeadersCustom = []
- # if length == 1:
- # Part = command.split('\r\n')
- i = 2
- rex = re.compile("[\a-z]{2,}: [\a-z]{3,}")
- while i < length:
- if rex.match(Part[i].lower()):
- Header = Part[i].split(' ')[0]
- Header_Name = Part[i].split(' ')[1]
- New = [Header.replace(':', ''), Header_Name]
- print(New)
- HeadersCustom.append(New)
- i += 1
- return HeadersCustom
- def method_checker(method):
- if method == "GET":
- return 1
- elif method == "DELETE" or method == "PUT" or method == "POST" or method == "HEAD":
- print("Error 501 Not Implemented Request")
- return 0
- else:
- print("Error 400 Bad Request")
- return 2
- pass
- def main():
- print("*" * 50)
- print(f"[LOG] Printing command line arguments [{', '.join(sys.argv)}]")
- check_file_name()
- print("*" * 50)
- print("\n")
- command = input("")
- check_http_request_validity(command)
- # This argument is optional, defaults to 18888
- # proxy_port_number = get_arg(1, 18888)
- # entry_point(proxy_port_number)
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement