From f6c2b9dd5b3f1b9fb1581531275dcc5ddbf7d545 Mon Sep 17 00:00:00 2001 From: Bram Prieshof Date: Mon, 21 Sep 2026 23:13:19 +0200 Subject: [PATCH] Added quality of life functions * Ability to use full path for image path * Ability to configure the wait time between images * Added protection from escaping the image directory --- README.md | 1 + config.json.example | 1 + server.py | 61 ++++++++++++++++++++++++++++++++++++++++++--- show.html | 9 ++++--- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5abab51..a9bde61 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ A chromecast ambient mode like Diashow 1. Install `pyinstaller` using `pip install -U pyinstaller` 2. Create binary using `pyinstaller server-pyinstaller.spec` 3. The binary is created in the dist folder, to use it create a config an the configured image folder here (or wherever you move the binary to) +4. Update the path to the images to a full path * if you wish to cleanup you can remove the build (and if you copied the binary somware else the dist folder) * Removing `pyinstaller` is safe you can use `pip uninstall -y altgraph setuptools packaging pyinstaller-hooks-contrib pyinstaller pywin32-ctypes pefile` diff --git a/config.json.example b/config.json.example index 98c37b3..cffb5ce 100644 --- a/config.json.example +++ b/config.json.example @@ -1,6 +1,7 @@ { "PORT":8000, "IMAGE_DIR":"images", + "IMAGE_TIMEOUT":Minutes the image is shown, "WeatherProvider":"HomeAssistant or MetNO", "METNO_LAT":"MetNO Weather GPS Latitude", "METNO_LON":"MetNO Weather GPS longitude", diff --git a/server.py b/server.py index d4f5c78..073e7aa 100644 --- a/server.py +++ b/server.py @@ -7,6 +7,8 @@ from PIL import Image from PIL.ExifTags import TAGS import urllib.parse from datetime import datetime +import mimetypes +from pathlib import Path # -- # Configuration @@ -21,6 +23,8 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): if self.path == "/": self.send_slideshow_page() + elif self.path == "/config.js": + self.send_clientconf() elif self.path == "/images.json": self.send_image_list() elif self.path == "/weather": @@ -30,8 +34,10 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler): self.get_METNOweather() elif self.path.startswith("/exif"): self.send_exif_data() + elif self.path.startswith("/img"): + self.send_image() else: - super().do_GET() + self.send_error(404, "Not found") #-- # Main Page @@ -49,13 +55,51 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler): self.end_headers() self.wfile.write(content) +#-- +# Image depot +#-- + def send_image(self): + parsed = urllib.parse.urlparse(self.path) + params = urllib.parse.parse_qs(parsed.query) + filename = params.get("file", [None])[0] + + if not filename: + self.send_error(400, "Missing ?file= parameter") + return + + base_dir = Path(cfg["IMAGE_DIR"]).resolve() + print(base_dir) + file_path = (base_dir / filename).resolve() + try: + file_path.relative_to(base_dir) + except ValueError: + self.send_error(403, "Forbidden") + return + + content_type, _ = mimetypes.guess_type(filename) + self.send_response(200) + self.send_header("Content-Type", content_type or "application/octet-stream") + self.end_headers() + with open(cfg['IMAGE_DIR'] + "/" + filename, 'rb') as file: + self.wfile.write(file.read()) + +#-- +# Client config +#-- + def send_clientconf(self): + self.send_response(200) + self.send_header("Content-type", "text/javascript") + self.end_headers() + response = f"""const ImgDuration = "{cfg["IMAGE_TIMEOUT"]}";""" + self.wfile.write(response.encode("utf-8")) + #-- # Image list #-- def send_image_list(self): files = os.listdir(cfg['IMAGE_DIR']) images = [ - f"/{cfg['IMAGE_DIR']}/{f}" + f"{f}" for f in files if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif", ".webp")) ] @@ -145,6 +189,7 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler): self.send_header("Content-Length", str(len(payload))) self.end_headers() self.wfile.write(payload) + #-- # Image info #-- @@ -157,7 +202,17 @@ class SlideshowHandler(http.server.SimpleHTTPRequestHandler): self.send_error(400, "Missing ?file= parameter") return - filepath = filename.lstrip("/") + base_dir = Path(cfg["IMAGE_DIR"]).resolve() + print(base_dir) + file_path = (base_dir / filename).resolve() + try: + file_path.relative_to(base_dir) + except ValueError: + self.send_error(403, "Forbidden") + return + + + filepath = cfg['IMAGE_DIR'] +"/"+ filename if not os.path.exists(filepath): self.send_error(404, "File not found") diff --git a/show.html b/show.html index b838165..730d113 100644 --- a/show.html +++ b/show.html @@ -134,6 +134,7 @@ +