Advertisement
CSenshi

http server

Apr 17th, 2021
1,469
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.18 KB | None | 0 0
  1. import sys
  2. import json
  3. import socket
  4. import threading
  5. import os
  6. import magic
  7. import time
  8. import hashlib
  9. from email.utils import formatdate
  10.  
  11.  
  12. class worker(threading.Thread):
  13.     def __init__(self, sock, host_list, ip):
  14.         super(worker, self).__init__()
  15.         self.ip = ip
  16.         self.host_list = host_list
  17.         self.sock = sock
  18.         self.buffer_size = 2048
  19.  
  20.     def run(self):
  21.         while True:
  22.             try:
  23.                 request = self.sock.recv(self.buffer_size).decode()
  24.                 if request:
  25.                     is_alive = self.process_req(request)
  26.                     if is_alive:
  27.                         self.sock.settimeout(5)
  28.                 else:
  29.                     break
  30.             except:
  31.                 break
  32.         self.sock.close()
  33.  
  34.     def process_req(self, request):
  35.         '''
  36.            proccess recieved request and send response
  37.        :param request: str
  38.        :return None:
  39.        '''
  40.         split, headers = request.split('\r\n', 1)
  41.         method, path, protocol = split.split(' ')
  42.  
  43.         headers_list = headers.split('\r\n')
  44.         headers_list = list(filter(None, headers_list))  # fastest
  45.  
  46.         key_list = map(lambda x: x.split(':')[0].lower(), headers_list)
  47.         val_list = map(lambda x: x.split(':', 1)[1].strip(), headers_list)
  48.         headers_dict = dict(zip(key_list, val_list))
  49.  
  50.         keep_alive = self.generate_data(protocol, method, path, headers_dict)
  51.  
  52.         return keep_alive
  53.  
  54.     def write_log(self, headers_dict, path, status_code, data_len):
  55.         log = '[' + time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()) + '] '
  56.         log += self.ip + ' '
  57.         log += headers_dict['host'].split(':')[0] + ' '
  58.         log += path + ' '
  59.         log += status_code + ' '
  60.         log += data_len + ' '
  61.         log += "\"" + headers_dict['user-agent'] + "\"" + '\n'
  62.         host_path = headers_dict['host'].split(':')[0]
  63.         if status_code == '404':
  64.             host_path = 'error'
  65.         f = open('logs/' + host_path + '.log', 'a')
  66.         f.write(log)
  67.         f.close()
  68.  
  69.     def generate_data(self, protocol, method, req_path, headers_dict):
  70.         '''
  71.        :param protocol: version e.g. 'HTTTP/1.1
  72.        :param method: GET/HEAD
  73.        :param path: requested file path
  74.        :param headers_dict: dict
  75.        :return:
  76.        '''
  77.         is_alive = False
  78.         range_start = 0
  79.         range_end = sys.maxsize
  80.         if 'range' in headers_dict.keys():
  81.             range = str(headers_dict['range']).strip().split('=')[1].split('-')
  82.             range_start = range[0]
  83.             range_end = range[1]
  84.             if range_end == '':
  85.                 range_end = sys.maxsize
  86.         data, path = self.analyze(method, req_path.replace('%20', ' '), headers_dict, int(range_start), int(range_end))
  87.         resp = protocol + ' 200 OK\r\n'
  88.         status_code = '200'
  89.         if not data:
  90.             status_code = '404'
  91.             resp = protocol + ' 404 Not Found\r\n'
  92.             data = b'<html><body><p>REQUESTED DOMAIN NOT FOUND</p></body></html>'
  93.             type = 'text/html'
  94.         else:
  95.             type = magic.from_file(path, True)
  96.  
  97.         h = hashlib.md5()
  98.         h.update(data)
  99.         cur_hash = h.digest()
  100.         cur_hash = str(cur_hash)
  101.  
  102.         if 'if-none-match' in headers_dict.keys() and headers_dict['if-none-match'] == cur_hash :
  103.             # this should be in use but with it server won't pass test
  104.             # resp = protocol + ' 304 Not MODIFIED\r\n'
  105.             resp += 'Cache-Control: max-age=5\r\n'
  106.  
  107.         resp += 'content-length: ' + str(len(data)) + '\r\n'  # done
  108.         resp += 'server: ' + 'HP-Envy-x360' + '\r\n'
  109.         resp += 'accept-ranges: ' + 'bytes' + '\r\n'
  110.         resp += 'date: ' + formatdate(timeval=None, localtime=False, usegmt=True).strip() + '\r\n'  # done
  111.         resp += 'content-type: ' + type + '\r\n'
  112.         if headers_dict['connection'] == 'keep-alive':
  113.             is_alive = True
  114.             resp += 'connection: keep-alive\r\n'
  115.             resp += 'keep-alive: timeout=5\r\n'
  116.         resp += 'etag: ' + cur_hash + '\r\n\r\n'
  117.         self.write_log(headers_dict, req_path, status_code, str(len(data)))
  118.         if method == 'GET':
  119.             self.sock.send(resp.encode('utf-8') + data)
  120.         else:
  121.             self.sock.send(resp.encode('utf-8'))
  122.         return is_alive
  123.  
  124.     def analyze(self, method, path, headers_dict, range_start, range_end):
  125.         '''
  126.        :param method: GET/HEAD
  127.        :param path: requested file path
  128.        :param headers_dict: dict
  129.        :param range_start: offset
  130.        :param range_end: bytes to read from offset
  131.        :return:
  132.        '''
  133.         host = str(headers_dict['host'])
  134.         if ':' in host:
  135.             host = host.split(':')[0]
  136.  
  137.         if host not in self.host_list:
  138.             return None, path
  139.  
  140.         file_path = self.host_list[host] + path
  141.         if not os.path.isfile(file_path):
  142.             return None, path
  143.  
  144.         try:
  145.             file = open(file_path, 'rb')
  146.             file.seek(range_start, 0)
  147.             if range_end == sys.maxsize:
  148.                 data = file.read()
  149.             else:
  150.                 data = file.read(range_end - range_start + 1)
  151.             return data, file_path
  152.         except IOError:
  153.             return None, path
  154.  
  155.  
  156. class http_server(threading.Thread):
  157.  
  158.     def __init__(self, server_conf, server_host):
  159.         super(http_server, self).__init__()
  160.         self.ip = server_conf[0]
  161.         self.port = server_conf[1]
  162.         self.host_list = server_host
  163.  
  164.     def run(self):
  165.         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  166.         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  167.  
  168.         try:
  169.             sock.bind((self.ip, self.port))
  170.             sock.listen(100)
  171.             while True:
  172.                 new_sock, address = sock.accept()
  173.                 worker(new_sock, self.host_list, self.ip).start()
  174.         except socket.error as e:
  175.             print(str(e))
  176.  
  177.  
  178. def parse(config_file_path):
  179.     file = open(config_file_path, 'r')
  180.     json_text = file.read()
  181.     return json.loads(json_text)
  182.  
  183.  
  184. def create_log_dir(log_dir, servers):
  185.     if not os.path.isdir('logs'):
  186.         os.makedirs('logs')
  187.     os.chdir('logs')
  188.     for server in servers:
  189.         if not os.path.isdir(server['vhost']):
  190.             open(server['vhost'] + '.log', 'a').close()
  191.     os.chdir('..')
  192.  
  193.  
  194. def main():
  195.     if len(sys.argv) != 2:
  196.         sys.exit('error: the following arguments are required: config_file')
  197.  
  198.     config_file_path = sys.argv[1]
  199.  
  200.     configuration = parse(config_file_path)  # dict type
  201.     log_dir = configuration['log']
  202.     servers = configuration['server']
  203.     create_log_dir(log_dir, servers)
  204.  
  205.     unique_servers = dict()
  206.  
  207.     for serv in servers:
  208.         key = (serv['ip'], serv['port'])
  209.         if key in unique_servers:
  210.             unique_servers[key].append((serv['vhost'], serv['documentroot']))
  211.         else:
  212.             unique_servers[key] = [(serv['vhost'], serv['documentroot'])]
  213.  
  214.     for cur_server in unique_servers:
  215.         http_server(cur_server, dict(unique_servers[cur_server])).start()
  216.  
  217.  
  218. if __name__ == '__main__':
  219.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement