5 Commits

Author SHA1 Message Date
22a37670f7 Updated README to reflect PyWebServer state 2025-05-03 22:16:11 +02:00
5014ff2a04 Quick #4 fix 2025-05-03 22:10:40 +02:00
e6b188196b Update README.md to reflect GitHub mirror 2025-05-03 16:28:07 +02:00
b1bf3825de README update 2025-04-14 08:21:51 +02:00
7ac160f625 Quick fix that adresses #1 2025-04-13 13:27:58 +02:00
3 changed files with 185 additions and 18 deletions

View File

@@ -1,2 +1,63 @@
# 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.
## Installing
Installing and running PyWebServer is very simple.
Assuming you're running Linux:
```bash
git clone https://git.novacow.ch/Nova/PyWebServer.git
cd ./PyWebServer/
```
Windows users, make sure you have installed Git, from there:
```powershell
git clone https://git.novacow.ch/Nova/PyWebServer.git
Set-Location .\PyWebServer\
```
From here, you should check from what directory you want to store the content in.
In this example, we'll use `./html/` (or `.\html\` for Windows users) from the perspective of the PyWebServer root dir.
To create this directory, do this:
```bash
mkdir ./html/
```
(This applies to both Windows and Linux)
Then, open `pywebsrv.conf` in your favorite text editor and change the `directory` key to the full path to the `./html/` you just created.
After that, put your files in and run this:
Linux:
```bash
python3 /path/to/pywebsrv.py
```
Windows:
```powershell
# If you have installed Python via the Microsoft Store:
python3 \path\to\pywebsrv.py
# Via the python.org website:
py \path\to\pywebsrv.py
```
## SSL Support
Currently PyWebServer warns about AutoCertGen not being installed. AutoCertGen currently is very unstable at the moment, and therefore is not available for download.
PyWebServer supports SSL/TLS for authentication via HTTPS. In the config file, you should enable the HTTPS port. After that you need to create the certificate.
Currently PyWebServer looks for the `cert.pem` and the `key.pem` files in the root directory of the installation.
PyWebServer comes with a test certificate, this certificate is self-signed, but doesn't have a matching issuer and subject. This is to prevent people from using it in production, even if they have disabled warnings of self-signed certificates.
## HTTP support
Currently PyWebServer only supports HTTP/1.1, this is very unlikely to change, as most of the modern web today still uses HTTP/1.1.
For methods PyWebServer only supports `GET`, this is being reworked though, check issue [#3](https://git.novacow.ch/Nova/PyWebServer/issues/3) for progress.
## Files support
Unlike other small web servers, PyWebServer has full support for binary files being sent and received (once that logic is put in) over HTTP.
## Support
PyWebServer will follow a standard support scheme.
### 1.x
For every 1.x version there will be support until 2 newer versions come out.
So that means that 1.0 will still be supported when 1.1 comes out, but no longer be supported when 1.2 comes out.
### 2.x
I am planning on releasing a 2.x version with will have a lot more advanced features, like nginx's server block emulation amongst other things.
When 2.0 will come out, the last version of 1.x will be supported for a while longer, but no new features will be added.

61
certgen.py Normal file
View File

@@ -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!")

View File

@@ -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 <https://www.gnu.org/licenses/>.
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"
@@ -45,7 +58,6 @@ class FileHandler:
)
def __init__(self, base_dir=None):
self.base_dir = base_dir or os.path.join(os.getcwd(), "html")
self.config_path = os.path.join(os.getcwd(), self.CONFIG_FILE)
def check_first_run(self):
@@ -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
@@ -125,6 +140,13 @@ class FileHandler:
or option == "allow-nohost"
):
return bool(int(value))
if option == "directory":
if value == "<Enter directory here>":
print(
"FATAL: You haven't set up PyWebServer! Please edit pywebsrv.conf!"
)
exit(1)
return value
return value
return None
@@ -133,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:
@@ -150,11 +169,11 @@ class RequestParser:
"""Parses the HTTP request line."""
try:
method, path, version = line.split(" ")
if path.endswith("/"):
path += "index.html"
return method, path, version
except ValueError:
return None, None, None
if path.endswith("/"):
path += "index.html"
return method, path, version
def is_method_allowed(self, method):
"""
@@ -251,7 +270,7 @@ class WebServer:
)
self.http_405_html = (
"<html><head><title>HTTP 405 - PyWebServer</title></head>"
"<body><center><h1>HTTP 404 - Method not allowed</h1><p>Running PyWebServer/1.1</p>"
"<body><center><h1>HTTP 405 - Method not allowed</h1><p>Running PyWebServer/1.1</p>"
"</center></body></html>"
)
@@ -306,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:
@@ -363,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)
@@ -399,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
@@ -429,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()
@@ -439,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