From 464f2c16f3fecfa0baa2786c65ad68f8729c1e25 Mon Sep 17 00:00:00 2001 From: hellerve Date: Thu, 3 May 2018 17:46:29 +0200 Subject: [PATCH] initial --- README.md | 8 ++++ serve.py | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 README.md create mode 100644 serve.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..cdfb9f0 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# serve + +My Python port of the webserver Gary Bernhardt built in [this +screencast](https://www.destroyallsoftware.com/screencasts/catalog/http-server-from-scratch). + +It is not spec-compliant, serves files from disk, is vulnerable to directory +traversal, and if those files are executable, executes them. It’s as terrible +as it sounds! diff --git a/serve.py b/serve.py new file mode 100644 index 0000000..023ffc4 --- /dev/null +++ b/serve.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +import os +import socket +import subprocess + +class Connection(): + def __init__(self, sock): + self.sock = sock + self.buffer = "" + + def read_until(self, sep): + while sep not in self.buffer: + self.buffer += self.sock.recv(4096) + res, self.buffer = self.buffer.split(sep, 1) + return res + + def read_line(self): + return self.read_until("\r\n") + + def read_request(self): + request_line = self.read_line() + method, path, version = request_line.split(" ", 3) + + headers = {} + + line = self.read_line() + while len(line): + k, v = line.split(":", 1) + headers[k] = v.strip() + line = self.read_line() + + # we do not read the body + + return Request(method, path, headers) + + +class Request(): + def __init__(self, method, path, headers): + self.method = method + self.path = path + self.headers = headers + + def __str__(self): + return "Request(method='{}' path='{}' headers='{}')".format(self.method, + self.path, + self.headers) + + +MSGS = { + 200: "OK", + 400: "Bad Request", + 401: "Unauthorized", + 403: "Forbidden", + 404: "Not Found", + 500: "Internal Server Error", +} + + +def respond(conn, status_code, content): + conn.send("HTTP/1.1 {} {}\r\n".format(status_code, MSGS[status_code]), 0) + conn.send("Content-Length: {}\r\n".format(len(content))) + conn.send("\r\n", 0) + conn.send(content, 0) + + +def respond_to(conn, req): + content = "" + try: + path = os.getcwd() + req.path + if os.path.exists(path): + if os.access(path, os.X_OK): + content = subprocess.check_output([path]) + else: + with open(path, 'r') as f: + content = f.read() + status_code = 200 + else: + status_code = 404 + except Exception as e: + content = str(e) + status_code = 500 + respond(conn, status_code, content) + + +def accept(s): + conn, addr_info = s.accept() + connection = Connection(conn) + req = connection.read_request() + respond_to(conn, req) + + +def accept_loop(s): + while True: + accept(s) + + +def listen(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) + s.bind(('', 8080)) + s.listen(5) + + accept_loop(s) + +if __name__ == '__main__': + listen()