Files
foldsite/src/routes/routes.py
Tanishq Dubey c99bced56e
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 50s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 56s
Release / build (push) Successful in 1m23s
Release / publish_head (push) Successful in 1m17s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 5m41s
Many fixes?
2025-07-09 18:28:27 -04:00

178 lines
7.6 KiB
Python

from pathlib import Path
from src.config.config import Configuration
from src.rendering.renderer import render_page, render_error_page
from flask import send_file, request
from src.rendering.image import generate_thumbnail
import os
class RouteManager:
"""
RouteManager is responsible for handling and validating file system paths for serving content, styles, and static files in a web application. It ensures that all requested paths are securely resolved within configured base directories, prevents path traversal attacks, and restricts access to hidden files or folders.
Args:
config (Configuration): The configuration object containing directory paths for content, templates, and styles.
Methods:
_validate_and_sanitize_path(base_dir, requested_path_str):
Validates and sanitizes a requested path to ensure it is within the specified base directory and not a hidden file/folder. Returns a resolved Path object or None if invalid.
_ensure_route(path):
Ensures the given path is valid and returns the corresponding Path object. Raises an Exception if the path is illegal.
default_route(path):
Handles the default route for serving content files. Returns a rendered page or an error page if the path is invalid or not found.
get_style(path):
Serves style files from the styles directory. Returns the file or an error page if the path is invalid or not found.
get_static(path):
Serves static files from the content directory. If the file is an image, generates and returns a thumbnail. Returns the file or an error page if the path is invalid or not found.
"""
def __init__(self, config: Configuration):
self.config = config
def _validate_and_sanitize_path(self, base_dir, requested_path_str: str):
"""
Validates and sanitizes a requested file system path to ensure it is safe and allowed.
This method resolves the requested path relative to a given base directory, ensuring:
- The resolved path exists.
- The resolved path is within the base directory (prevents directory traversal attacks).
- The path does not access hidden files or directories (those starting with '___').
Args:
base_dir (str or Path): The base directory against which the requested path is resolved.
requested_path_str (str): The user-supplied path to validate and sanitize.
Returns:
Path or None: The resolved and validated Path object if the path is safe and allowed;
otherwise, None if the path is invalid, does not exist, attempts traversal,
or accesses hidden files/directories.
"""
try:
base_dir = Path(base_dir).resolve(strict=True)
# a requested path of "" or "." should resolve to the base directory
if not requested_path_str:
requested_path_str = "."
secure_path = (base_dir / requested_path_str).resolve(strict=True)
except FileNotFoundError:
return None # Path does not exist
# The most important check: ensure the resolved path is inside the base directory.
if not secure_path.is_relative_to(base_dir):
print(f"Illegal path traversal attempt: {requested_path_str}")
return None
# Check for hidden files/folders (starting with '___')
relative_parts = secure_path.relative_to(base_dir).parts
# Also check the final component for the case where path is the base_dir itself.
if any(
part.startswith("___") for part in relative_parts
) or secure_path.name.startswith("___"):
print(f"Illegal access to hidden path: {requested_path_str}")
return None
return secure_path
def _ensure_route(self, path: str):
file_path = self._validate_and_sanitize_path(self.config.content_dir, path)
if not file_path:
raise Exception("Illegal path")
return file_path
def default_route(self, path: str):
"""
Handles the default route for serving content pages.
Attempts to resolve the given path to a file within the content directory.
If the path is empty, defaults to "index.md". If the file is not found or an error occurs,
renders a 404 error page. Otherwise, renders the requested page using the specified
template and style directories.
Args:
path (str): The requested path to resolve and serve.
Returns:
Response: The rendered page or an error page if the file is not found.
"""
try:
file_path = self._ensure_route(path if path else "index.md")
except Exception as _:
return render_error_page(
404,
"Not Found",
"The requested resource was not found on this server.",
self.config.templates_dir,
)
return render_page(
file_path,
base_path=self.config.content_dir,
template_path=self.config.templates_dir,
style_path=self.config.styles_dir,
)
def get_style(self, path: str):
"""
Retrieves and serves a style file from the configured styles directory.
Args:
path (str): The relative path to the requested style file.
Returns:
Response: A Flask response object containing the requested file if found,
or an error page with a 404 status code if the file does not exist.
"""
file_path = self._validate_and_sanitize_path(self.config.styles_dir, path)
if not file_path:
return render_error_page(
404,
"Not Found",
"The requested resource was not found on this server.",
self.config.templates_dir,
)
return send_file(file_path)
def get_static(self, path: str):
"""
Serves static files from the configured content directory.
If the requested file is an image (JPEG, PNG, or GIF), generates and returns a thumbnail
with a maximum width specified by the 'max_width' query parameter (default: 2048).
Otherwise, serves the file as-is.
Args:
path (str): The relative path to the requested static file.
Returns:
Response:
- If the file is not found or invalid, returns a rendered 404 error page.
- If the file is an image, returns the thumbnail bytes with appropriate headers.
- Otherwise, returns the file using Flask's send_file.
"""
file_path = self._validate_and_sanitize_path(self.config.content_dir, path)
if not file_path:
return render_error_page(
404,
"Not Found",
"The requested resource was not found on this server.",
self.config.templates_dir,
)
# Check to see if the file is an image, if it is, render a thumbnail
if file_path.suffix.lower() in [".jpg", ".jpeg", ".png", ".gif"]:
max_width = request.args.get("max_width", default=2048, type=int)
thumbnail_bytes, img_format = generate_thumbnail(
str(file_path), 10, 2048, max_width
)
return (
thumbnail_bytes,
200,
{
"Content-Type": f"image/{img_format.lower()}",
"cache-control": "public, max-age=31536000",
},
)
return send_file(file_path)