1 Commits
1.2.1 ... 1.3.0

Author SHA1 Message Date
a36141edd0 release for 1.3.0
minor update, got ua blocking working and match statements in config
2025-07-22 16:35:39 +02:00

View File

@@ -114,7 +114,8 @@ class FileHandler:
"allow-localhost", "allow-localhost",
"disable-autocertgen", "disable-autocertgen",
"key-file", "key-file",
"cert-file" "cert-file",
"block-ua"
] ]
if option not in valid_options: if option not in valid_options:
return None return None
@@ -131,6 +132,19 @@ class FileHandler:
if option == "host": if option == "host":
seperated_values = value.split(",", -1) seperated_values = value.split(",", -1)
return [value.lower() for value in seperated_values] return [value.lower() for value in seperated_values]
if option == "block-ua":
seperated_values = value.split(",", -1)
host_to_match = []
literal_blocks = []
for val in seperated_values:
if val.startswith("match(") and val.endswith(")"):
idx = val.index("(")
idx2 = val.index(")")
ua_to_match = val[idx+1:idx2]
host_to_match.append(ua_to_match)
else:
literal_blocks.append(val)
return host_to_match, literal_blocks
if option == "port" or option == "port-https": if option == "port" or option == "port-https":
return int(value) return int(value)
if ( if (
@@ -172,6 +186,16 @@ class RequestParser:
path += "index.html" path += "index.html"
return method, path, version return method, path, version
def ua_blocker(self, ua):
"""Parses and matches UA to block"""
match, literal = self.file_handler.read_config("block-ua")
if ua in literal:
return False
for _ua in match:
if _ua.lower() in ua.lower():
return False
return True
def is_method_allowed(self, method): def is_method_allowed(self, method):
""" """
Checks if the HTTP method is allowed. Checks if the HTTP method is allowed.
@@ -303,7 +327,6 @@ class WebServer:
while self.running: while self.running:
try: try:
conn, addr = self.http_socket.accept() conn, addr = self.http_socket.accept()
print(f"HTTP connection received from {addr}")
self.handle_connection(conn, addr) self.handle_connection(conn, addr)
except Exception as e: except Exception as e:
print(f"HTTP error: {e}") print(f"HTTP error: {e}")
@@ -316,7 +339,6 @@ class WebServer:
while self.running: while self.running:
try: try:
conn, addr = self.https_socket.accept() conn, addr = self.https_socket.accept()
print(f"HTTPS connection received from {addr}")
self.handle_connection(conn, addr) self.handle_connection(conn, addr)
except Exception as e: except Exception as e:
print( print(
@@ -329,6 +351,11 @@ class WebServer:
try: try:
data = conn.recv(512) data = conn.recv(512)
request = data.decode(errors="ignore") request = data.decode(errors="ignore")
if not data:
response = self.build_response(400, "Bad Request") # user did fucky-wucky
elif len(data) > 8192:
response = self.build_response(413, "Request too long")
else:
response = self.handle_request(request, addr) response = self.handle_request(request, addr)
if isinstance(response, str): if isinstance(response, str):
@@ -341,12 +368,7 @@ class WebServer:
conn.close() conn.close()
def handle_request(self, data, addr): def handle_request(self, data, addr):
print(f"len data: {len(data)}") print(f"data: {data}")
if not data:
return self.build_response(400, "Bad Request") # user did fucky-wucky
if len(data) > 8192:
return self.build_response(413, "Request too long")
request_line = data.splitlines()[0] request_line = data.splitlines()[0]
# Extract host from headers, never works though # Extract host from headers, never works though
@@ -364,17 +386,32 @@ class WebServer:
400, self.no_host_req_response.encode() 400, self.no_host_req_response.encode()
) )
for line in data.splitlines():
if "User-Agent" in line:
ua = line.split(":", 1)[1].strip()
allowed = self.parser.ua_blocker(ua)
if not allowed:
return self.build_response(
403, "This UA has been blocked by the owner of this site."
)
break
else:
return self.build_response(
400, "You cannot connect without a User-Agent."
)
method, path, version = self.parser.parse_request_line(request_line) method, path, version = self.parser.parse_request_line(request_line)
if not all([method, path, version]):
return self.build_response(400, "Bad Request")
# Figure out a better way to reload config # Figure out a better way to reload config
if path == "/?pywebsrv_reload_conf=1": if path == "/?pywebsrv_reload_conf=1":
print("Got reload command! Reloading configuration...") print("Got reload command! Reloading configuration...")
self.file_handler.base_dir = self.file_handler.read_config("directory") self.file_handler = FileHandler()
self.parser = RequestParser()
return self.build_response(302, "") return self.build_response(302, "")
if not all([method, path, version]):
return self.build_response(400, "Bad Request")
if not self.parser.is_method_allowed( if not self.parser.is_method_allowed(
method method
): ):
@@ -397,6 +434,9 @@ class WebServer:
# A really crude implementation of binary files. Later in 2.0 I'll actually # A really crude implementation of binary files. Later in 2.0 I'll actually
# make this useful. # make this useful.
mimetype = mimetype[0] mimetype = mimetype[0]
if mimetype is None:
# We have to assume it's binary.
return self.build_binary_response(200, file_content, "application/octet-stream")
if "text/" not in mimetype: if "text/" not in mimetype:
return self.build_binary_response(200, file_content, mimetype) return self.build_binary_response(200, file_content, mimetype)
@@ -410,7 +450,7 @@ class WebServer:
403: "Forbidden", 403: "Forbidden",
404: "Not Found", 404: "Not Found",
405: "Method Not Allowed", 405: "Method Not Allowed",
500: "Internal Server Error", 500: "Internal Server Error"
} }
status_message = messages.get(status_code) status_message = messages.get(status_code)
headers = ( headers = (
@@ -421,6 +461,7 @@ class WebServer:
f"Connection: close\r\n\r\n" f"Connection: close\r\n\r\n"
# Connection close is done because it is way easier to implement. # Connection close is done because it is way easier to implement.
# It's not like this program will see production use anyway. # It's not like this program will see production use anyway.
# Tbh when i'll implement HTTP2
) )
return headers.encode() + binary_data return headers.encode() + binary_data
@@ -440,7 +481,7 @@ class WebServer:
405: "Method Not Allowed", 405: "Method Not Allowed",
413: "Payload Too Large", 413: "Payload Too Large",
500: "Internal Server Error", 500: "Internal Server Error",
635: "Go Away", 635: "Go Away"
} }
status_message = messages.get(status_code) status_message = messages.get(status_code)