diff --git a/README.md b/README.md index c3eb0cb..5843c44 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,27 @@ promised features missing, but I'm very much working on it live! Every save I do increments the build number by 1, I won't publish all of them, but most of them will be published. Once a milestone is hit (e.g. a new feature fully implemented), I'll publish a release! +## Currently working features: +* New configuration is ~75% done, most features work. +* Fixed **A LOT** of unreported bugs from the old code. +* More resilliency against errors. +* Improved security. + +## Project status: +Amethyst will stay in beta for a while, I want all features to work, put I will make pre-release versions that are mostly stable. +They can be found as the `amethyst-prerel-0.a.b` releases. I won't guarantee 100% stability, but waay more than just some random build. + +## Install instructions: +Install Python, and change the provided config. + +## Minimum requirements: +Python 3.8+ +And whatever PC that happens to run that. +I recommend Python 3.10 or above though, with a PC running: +* Windows 8.1+ +* macOS 10.15+ +* Linux 4.19+ +* FreeBSD 13.2R+ +* Some other somewhat recent OS. + ## Currently W.I.P. Check back later! diff --git a/pywebsrv.py b/pywebsrv.py index f2442df..0ce8104 100644 --- a/pywebsrv.py +++ b/pywebsrv.py @@ -35,6 +35,8 @@ Library aswell as a standalone script: You can easily get access to other parts of the script if you need it. TODO: actually put normal comments in + +TODO: INPROG: add typing to all code, new code will feature it by default. """ import os @@ -42,7 +44,8 @@ import mimetypes import threading import ssl import socket -import re + +# import re import signal import sys @@ -56,7 +59,7 @@ except ImportError: # ) pass -AMETHYST_BUILD_NUMBER = "0039" +AMETHYST_BUILD_NUMBER = "0045" AMETHYST_REPO = "https://git.novacow.ch/Nova/PyWebServer/" @@ -253,15 +256,19 @@ class RequestParser: try: method, path, version = line.split(" ") except ValueError: - return None, None, None - if path.endswith("/"): + return "DELETE", "/this/is/a/bogus/request", "HTTP/1.0" + if path.endswith("/") and "." not in path: path += "index.html" return method, path, version - def ua_blocker(self, ua, host=None): + def ua_is_allowed(self, ua, host=None): """Parses and matches UA to block""" + # return True del host - match, literal = self.file_handler.read_config("block-ua") + _list = self.file_handler.read_config("block-ua") + if _list is None: + return True + match, literal = self.file_handler.parse_match_blocks(_list) if ua in literal: return False for _ua in match: @@ -400,8 +407,6 @@ class WebServer: http_thread = threading.Thread(target=self.start_http, daemon=True) https_thread = threading.Thread(target=self.start_https, daemon=True) - # ipv6http_thread = threading.Thread(target=self.start_http_ipv6, daemon=True) - # ipv6https_thread = threading.Thread(target=self.start_https_ipv6, daemon=True) if https is True: if self.skip_ssl is True: @@ -410,15 +415,11 @@ class WebServer: if yn.lower() == "n": exit(1) https_thread.start() - # ipv6https_thread.start() if http is True: - # ipv6http_thread.start() http_thread.start() http_thread.join() https_thread.join() - # ipv6http_thread.join() - # ipv6https_thread.join() def start_http(self): self.http_socket.listen(5) @@ -432,32 +433,6 @@ class WebServer: except OSError: break - def start_http_ipv6(self): - self.ipv6http_socket.listen(5) - print(f"IPv6 HTTP server listening on port {self.http_port}...") - while self.running: - try: - conn, addr = self.ipv6http_socket.accept() - self.handle_connection(conn, addr) - except Exception as e: - print(f"HTTP error: {e}") - except OSError: - break - - def start_https_ipv6(self): - self.ipv6https_socket.listen(5) - print(f"IPv6 HTTPS server listening on port {self.https_port}...") - while self.running: - try: - conn, addr = self.ipv6https_socket.accept() - self.handle_connection(conn, addr) - except Exception as e: - print( - f"HTTPS error: {e}" - ) # be ready for ssl errors if you use a self-sign!! - except OSError: - break - def start_https(self): self.https_socket.listen(5) print(f"HTTPS server listening on port {self.https_port}...") @@ -491,6 +466,13 @@ class WebServer: conn.sendall(response) except Exception as e: print(f"Error handling connection: {e}") + response = self.build_response( + 500, + "Amethyst is currently unable to serve your request. Below is debug info.\r\n" + f"Error: {e}; Version: amethyst-b{AMETHYST_BUILD_NUMBER}\r\n" + "You cannot do anything at this time, the server owner has made a misconfiguration or there is a bug in the program", + ) + conn.sendall(response) finally: conn.close() @@ -514,7 +496,7 @@ class WebServer: for line in data.splitlines(): if "User-Agent" in line: ua = line.split(":", 1)[1].strip() - allowed = self.parser.ua_blocker(ua) + allowed = self.parser.ua_is_allowed(ua) if not allowed: return self.build_response( 403, "This UA has been blocked by the owner of this site." @@ -540,6 +522,8 @@ class WebServer: if ":" in host: host2 = host.rsplit(":", 1)[0] + else: + host2 = host directory = ( self.file_handler.read_new_config("directory", host2) @@ -556,12 +540,10 @@ class WebServer: if file_content == 500: return self.build_response( 500, - "PyWebServer has encountered a fatal error and cannot serve " + "Amethyst has encountered a fatal error and cannot serve " "your request. Contact the owner with this error: FATAL_FILE_RO_ACCESS", ) # When there was an issue with reading we throw this. - # A really crude implementation of binary files. Later in 2.0 I'll actually - # make this useful. mimetype = mimetype[0] if mimetype is None: # We have to assume it's binary. @@ -592,7 +574,6 @@ class WebServer: f"Connection: close\r\n\r\n" # Connection close is done because it is way easier to implement. # It's not like this program will see production use anyway. - # Tbh when i'll implement HTTP2 ) return headers.encode() + binary_data