From a0f972f38ab60cf28424ff6fb11ee85ed1c5c962 Mon Sep 17 00:00:00 2001 From: lelo Date: Sun, 18 May 2025 22:25:50 +0200 Subject: [PATCH] avoid breaking out into filestructure --- app.py | 34 ++++++++++++++++++++++++++++++++++ static/app.js | 7 ++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 997cf0f..5d3404c 100755 --- a/app.py +++ b/app.py @@ -15,6 +15,7 @@ import geoip2.database from functools import lru_cache from urllib.parse import urlparse, unquote from werkzeug.middleware.proxy_fix import ProxyFix +from pathlib import Path import re import qrcode import base64 @@ -24,6 +25,7 @@ import analytics as a import folder_secret_config_editor as fsce app_config = auth.return_app_config() +BASE_DIR = os.path.realpath(app_config['BASE_DIR']) cache_audio = diskcache.Cache('./filecache_audio', size_limit= app_config['filecache_size_limit_audio'] * 1024**3) cache_image = diskcache.Cache('./filecache_image', size_limit= app_config['filecache_size_limit_image'] * 1024**3) @@ -90,6 +92,27 @@ def get_cached_image(size): resized_img.save(img_byte_arr, format='PNG') return img_byte_arr.getvalue() +def check_path(access_path: str) -> Path: + """ + Take an absolute access_path, then ensure it lives inside BASE_DIR. + Raises ValueError or PermissionError on failure. + """ + p = Path(access_path) + if not p.is_absolute(): + raise ValueError(f"Path {access_path} is not a valid absolute path") + + # Resolve symlinks & eliminate “..” components + candidate = p.resolve() + base = Path(BASE_DIR).resolve() + + try: + # Will raise ValueError if candidate is not under base + candidate.relative_to(base) + except ValueError: + raise PermissionError(f"Access to {access_path} is forbidden") + + return candidate + def list_directory_contents(directory, subpath): """ List only the immediate contents of the given directory. @@ -236,6 +259,12 @@ def api_browse(subpath): directory = os.path.join(base_path, *relative_parts) playfile = None + + try: + directory = check_path(directory) + except (ValueError, PermissionError) as e: + return jsonify({'error': str(e)}), 403 + # Check if the constructed directory exists. if not os.path.isdir(directory): # Assume the last segment is a filename; remove it. @@ -270,6 +299,11 @@ def serve_file(subpath): root, *relative_parts = subpath.split('/') base_path = session['folders'].get(root) full_path = os.path.join(base_path or '', *relative_parts) + + try: + full_path = check_path(full_path) + except (ValueError, PermissionError) as e: + return jsonify({'error': str(e)}), 403 if not os.path.isfile(full_path): app.logger.error(f"File not found: {full_path}") diff --git a/static/app.js b/static/app.js index c3ff875..3c4b1e1 100644 --- a/static/app.js +++ b/static/app.js @@ -213,7 +213,12 @@ function loadDirectory(subpath) { .then(data => { clearTimeout(spinnerTimer); hideSpinner(); - renderContent(data); + if (data.breadcrumbs) { + renderContent(data); + } else if (data.error) { + document.getElementById('content').innerHTML = `
${data.error}
`; + return; + } if (data.playfile) { const playFileLink = document.querySelector(`.play-file[data-url="${data.playfile}"]`); if (playFileLink) {