Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import sys
- import json
- import socket
- import threading
- import os
- import magic
- import time
- import hashlib
- from email.utils import formatdate
- class worker(threading.Thread):
- def __init__(self, sock, host_list, ip):
- super(worker, self).__init__()
- self.ip = ip
- self.host_list = host_list
- self.sock = sock
- self.buffer_size = 2048
- def run(self):
- while True:
- try:
- request = self.sock.recv(self.buffer_size).decode()
- if request:
- is_alive = self.process_req(request)
- if is_alive:
- self.sock.settimeout(5)
- else:
- break
- except:
- break
- self.sock.close()
- def process_req(self, request):
- '''
- proccess recieved request and send response
- :param request: str
- :return None:
- '''
- split, headers = request.split('\r\n', 1)
- method, path, protocol = split.split(' ')
- headers_list = headers.split('\r\n')
- headers_list = list(filter(None, headers_list)) # fastest
- key_list = map(lambda x: x.split(':')[0].lower(), headers_list)
- val_list = map(lambda x: x.split(':', 1)[1].strip(), headers_list)
- headers_dict = dict(zip(key_list, val_list))
- keep_alive = self.generate_data(protocol, method, path, headers_dict)
- return keep_alive
- def write_log(self, headers_dict, path, status_code, data_len):
- log = '[' + time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()) + '] '
- log += self.ip + ' '
- log += headers_dict['host'].split(':')[0] + ' '
- log += path + ' '
- log += status_code + ' '
- log += data_len + ' '
- log += "\"" + headers_dict['user-agent'] + "\"" + '\n'
- host_path = headers_dict['host'].split(':')[0]
- if status_code == '404':
- host_path = 'error'
- f = open('logs/' + host_path + '.log', 'a')
- f.write(log)
- f.close()
- def generate_data(self, protocol, method, req_path, headers_dict):
- '''
- :param protocol: version e.g. 'HTTTP/1.1
- :param method: GET/HEAD
- :param path: requested file path
- :param headers_dict: dict
- :return:
- '''
- is_alive = False
- range_start = 0
- range_end = sys.maxsize
- if 'range' in headers_dict.keys():
- range = str(headers_dict['range']).strip().split('=')[1].split('-')
- range_start = range[0]
- range_end = range[1]
- if range_end == '':
- range_end = sys.maxsize
- data, path = self.analyze(method, req_path.replace('%20', ' '), headers_dict, int(range_start), int(range_end))
- resp = protocol + ' 200 OK\r\n'
- status_code = '200'
- if not data:
- status_code = '404'
- resp = protocol + ' 404 Not Found\r\n'
- data = b'<html><body><p>REQUESTED DOMAIN NOT FOUND</p></body></html>'
- type = 'text/html'
- else:
- type = magic.from_file(path, True)
- h = hashlib.md5()
- h.update(data)
- cur_hash = h.digest()
- cur_hash = str(cur_hash)
- if 'if-none-match' in headers_dict.keys() and headers_dict['if-none-match'] == cur_hash :
- # this should be in use but with it server won't pass test
- # resp = protocol + ' 304 Not MODIFIED\r\n'
- resp += 'Cache-Control: max-age=5\r\n'
- resp += 'content-length: ' + str(len(data)) + '\r\n' # done
- resp += 'server: ' + 'HP-Envy-x360' + '\r\n'
- resp += 'accept-ranges: ' + 'bytes' + '\r\n'
- resp += 'date: ' + formatdate(timeval=None, localtime=False, usegmt=True).strip() + '\r\n' # done
- resp += 'content-type: ' + type + '\r\n'
- if headers_dict['connection'] == 'keep-alive':
- is_alive = True
- resp += 'connection: keep-alive\r\n'
- resp += 'keep-alive: timeout=5\r\n'
- resp += 'etag: ' + cur_hash + '\r\n\r\n'
- self.write_log(headers_dict, req_path, status_code, str(len(data)))
- if method == 'GET':
- self.sock.send(resp.encode('utf-8') + data)
- else:
- self.sock.send(resp.encode('utf-8'))
- return is_alive
- def analyze(self, method, path, headers_dict, range_start, range_end):
- '''
- :param method: GET/HEAD
- :param path: requested file path
- :param headers_dict: dict
- :param range_start: offset
- :param range_end: bytes to read from offset
- :return:
- '''
- host = str(headers_dict['host'])
- if ':' in host:
- host = host.split(':')[0]
- if host not in self.host_list:
- return None, path
- file_path = self.host_list[host] + path
- if not os.path.isfile(file_path):
- return None, path
- try:
- file = open(file_path, 'rb')
- file.seek(range_start, 0)
- if range_end == sys.maxsize:
- data = file.read()
- else:
- data = file.read(range_end - range_start + 1)
- return data, file_path
- except IOError:
- return None, path
- class http_server(threading.Thread):
- def __init__(self, server_conf, server_host):
- super(http_server, self).__init__()
- self.ip = server_conf[0]
- self.port = server_conf[1]
- self.host_list = server_host
- def run(self):
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- try:
- sock.bind((self.ip, self.port))
- sock.listen(100)
- while True:
- new_sock, address = sock.accept()
- worker(new_sock, self.host_list, self.ip).start()
- except socket.error as e:
- print(str(e))
- def parse(config_file_path):
- file = open(config_file_path, 'r')
- json_text = file.read()
- return json.loads(json_text)
- def create_log_dir(log_dir, servers):
- if not os.path.isdir('logs'):
- os.makedirs('logs')
- os.chdir('logs')
- for server in servers:
- if not os.path.isdir(server['vhost']):
- open(server['vhost'] + '.log', 'a').close()
- os.chdir('..')
- def main():
- if len(sys.argv) != 2:
- sys.exit('error: the following arguments are required: config_file')
- config_file_path = sys.argv[1]
- configuration = parse(config_file_path) # dict type
- log_dir = configuration['log']
- servers = configuration['server']
- create_log_dir(log_dir, servers)
- unique_servers = dict()
- for serv in servers:
- key = (serv['ip'], serv['port'])
- if key in unique_servers:
- unique_servers[key].append((serv['vhost'], serv['documentroot']))
- else:
- unique_servers[key] = [(serv['vhost'], serv['documentroot'])]
- for cur_server in unique_servers:
- http_server(cur_server, dict(unique_servers[cur_server])).start()
- if __name__ == '__main__':
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement