From e6b188196ba3b6d4cc3453aae41939b00d7485ed Mon Sep 17 00:00:00 2001 From: Nova Date: Sat, 3 May 2025 16:28:07 +0200 Subject: [PATCH 1/3] Update README.md to reflect GitHub mirror --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 61994b3..b98255f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # PyWebServer +## GitHub +The upstream of this project is on my own [Gitea instance](https://git.novacow.ch/Nova/PyWebServer/). +Because of that I'll mostly reply to issues and PRs there, you can submit issues and PRs on GitHub, but it might take longer before I read it. + ## Installing Installing and running PyWebServer is very simple. Assuming you're running Linux: From 5014ff2a04615754bf331156fa1215f19b83ebc1 Mon Sep 17 00:00:00 2001 From: Nova Date: Sat, 3 May 2025 22:10:40 +0200 Subject: [PATCH 2/3] Quick #4 fix --- certgen.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++ pywebsrv.py | 70 +++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 certgen.py diff --git a/certgen.py b/certgen.py new file mode 100644 index 0000000..26821ea --- /dev/null +++ b/certgen.py @@ -0,0 +1,61 @@ +from cryptography import x509 +from cryptography.x509.oid import NameOID +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +import datetime + + +class AutoCertGen: + def __init__(self): + pass + + def gen_cert(): + # Generate private key + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + + # Define subject and issuer (self-signed) + subject = issuer = x509.Name( + [ + x509.NameAttribute(NameOID.COUNTRY_NAME, "ZZ"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Some province"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "Some place"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Some org"), + x509.NameAttribute(NameOID.COMMON_NAME, "localhost"), + ] + ) + + # Create certificate + certificate = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(private_key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.datetime.utcnow()) + .not_valid_after( + datetime.datetime.utcnow() + datetime.timedelta(days=365) + ) # 1 year validity + .add_extension( + x509.SubjectAlternativeName([x509.DNSName("localhost")]), critical=False + ) + .sign(private_key, hashes.SHA256()) + ) + + # Save private key + with open("key.pem", "wb") as f: + f.write( + private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + ) + + # Save certificate + with open("certificate.pem", "wb") as f: + f.write(certificate.public_bytes(serialization.Encoding.PEM)) + + print("Self-signed certificate and private key generated for HTTPS server!") diff --git a/pywebsrv.py b/pywebsrv.py index fa40f93..a095a30 100644 --- a/pywebsrv.py +++ b/pywebsrv.py @@ -1,4 +1,17 @@ """ +License: + PyWebServer + Copyright (C) 2025 Nova + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + + Contact: + E-mail: nova@novacow.ch + This is PyWebServer, an ultra minimalist webserver, meant to still have a lot standard webserver features. A comprehensive list is below: Features: @@ -28,7 +41,7 @@ import signal import sys try: - from autocertgen import AutoCertGen + from certgen import AutoCertGen except ImportError: print( "WARN: You need the AutoCertGen plugin! Please install it from\n" @@ -46,7 +59,6 @@ class FileHandler: def __init__(self, base_dir=None): self.config_path = os.path.join(os.getcwd(), self.CONFIG_FILE) - self.base_dir = self.read_config("directory") def check_first_run(self): if not os.path.isfile(self.config_path): @@ -58,6 +70,9 @@ class FileHandler: with open(self.config_path, "w") as f: f.write(self.DEFAULT_CONFIG.format(cwd=os.getcwd())) + def didnt_confirm(self): + os.remove(self.config_path) + def read_file(self, file_path): if "../" in file_path: return 403 @@ -127,7 +142,9 @@ class FileHandler: return bool(int(value)) if option == "directory": if value == "": - print("FATAL: You haven't set up PyWebServer! Please edit pywebsrv.conf!") + print( + "FATAL: You haven't set up PyWebServer! Please edit pywebsrv.conf!" + ) exit(1) return value return value @@ -138,10 +155,7 @@ class FileHandler: Generate some self-signed certificates using AutoCertGen """ autocert = AutoCertGen() - pk = autocert.generate_private_key() - sub, iss = autocert.generate_issuer_and_subject() - cert = autocert.build_cert(pk, iss, sub) - autocert.write_cert(pk, cert) + autocert.gen_cert() class RequestParser: @@ -311,12 +325,12 @@ class WebServer: def handle_connection(self, conn, addr): try: - data = conn.recv(512) # why? well internet and tutiorials + data = conn.recv(512) request = data.decode(errors="ignore") response = self.handle_request(request, addr) if isinstance(response, str): - response = response.encode() # if we send text this shouldn't explode + response = response.encode() conn.sendall(response) except Exception as e: @@ -368,9 +382,10 @@ class WebServer: 500, "PyWebServer has encountered a fatal error and cannot serve " "your request. Contact the owner with this error: FATAL_FILE_RO_ACCESS", - ) # The user did no fucky-wucky, but the server fucking exploded. + ) # When there was an issue with reading we throw this. - # (try to) detect binary files (eg, mp3) and serve them correctly + # A really crude implementation of binary files. Later in 2.0 I'll actually + # make this useful. if path.endswith((".mp3", ".png", ".jpg", ".jpeg", ".gif")): return self.build_binary_response(200, file_content, path) @@ -404,12 +419,18 @@ class WebServer: f"Server: PyWebServer/1.1\r\n" f"Content-Type: {content_type}\r\n" f"Content-Length: {len(binary_data)}\r\n" - f"Connection: close\r\n\r\n" # connection close bcuz im lazy + 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. ) return headers.encode() + binary_data @staticmethod def build_response(status_code, body): + """ + For textfiles we'll not have to guess MIME-types, though the other function + build_binary_response will be merged in here anyway. + """ messages = { 200: "OK", 304: "Not Modified", # TODO KEKL @@ -434,8 +455,7 @@ class WebServer: return headers + body def shutdown(self, signum, frame): - print(f"\nRecieved signal {signum}") - print("\nShutting down server...") + print("\nRecieved signal to exit!\nShutting down server...") self.running = False self.http_socket.close() self.https_socket.close() @@ -444,7 +464,27 @@ class WebServer: def main(): file_handler = FileHandler() - file_handler.check_first_run() + first_run = file_handler.check_first_run() + if first_run is True: + print( + "*******************************************************************\n" + "* WARNING!! *\n" + "*******************************************************************\n" + "You have installed PyWebServer for the first time!\n" + "PyWebServer comes with test keys and certificates!\n" + "THESE SHOULD UNDER NO CIRCUMSTANCE BE USED IN ANYTHING BUT LOCAL TESTING!!!\n" + "IF YOU DON'T FOLLOW THESE INSTRUCTIONS YOU ARE PUTTING ALL YOUR TRAFFIC IN DANGER!!!\n" + "PLEASE REMOVE THEM ASAP IF YOU'RE USING THIS IN ANY FORM OF PRODUCTION!!!\n" + "*******************************************************************\n" + "* WARNING!! *\n" + "*******************************************************************\n" + ) + confirm = input("Do you understand? [y/N] ") + if confirm != "y": + print("User did not confirm, exiting!") + file_handler.didnt_confirm() + exit(1) + file_handler.base_dir = file_handler.read_config("directory") http_port = file_handler.read_config("port") or 8080 https_port = file_handler.read_config("port-https") or 8443 http_enabled = file_handler.read_config("http") or True From 22a37670f7eba30f90945c9777a35817f991e5b7 Mon Sep 17 00:00:00 2001 From: Nova Date: Sat, 3 May 2025 22:16:11 +0200 Subject: [PATCH 3/3] Updated README to reflect PyWebServer state --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b98255f..4703bae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # PyWebServer +# PyWebServer is undergoing major security updates! +# Please use commit [7ac160f625](https://git.novacow.ch/Nova/PyWebServer/src/commit/7ac160f6259e637c2337a672e56f105f4cdd2d2a) as the source for now!! + ## GitHub The upstream of this project is on my own [Gitea instance](https://git.novacow.ch/Nova/PyWebServer/). Because of that I'll mostly reply to issues and PRs there, you can submit issues and PRs on GitHub, but it might take longer before I read it.