File size: 1,854 Bytes
dc06d4c
 
 
 
 
 
 
 
 
 
 
c6a3f44
dc06d4c
 
 
 
 
 
 
 
 
c6a3f44
dc06d4c
 
 
 
 
 
 
c6a3f44
dc06d4c
 
 
 
 
 
 
 
c6a3f44
dc06d4c
 
 
 
 
 
 
 
 
c6a3f44
dc06d4c
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import uuid
from pathlib import Path

import openpyxl
from werkzeug.utils import secure_filename


ALLOWED_EXCEL_EXTENSIONS = (".xlsx", ".xlsm")


def save_uploaded_excel(uploaded, upload_dir: Path):
    """Validate and save an uploaded Excel file with a collision-safe name."""
    if not uploaded or not uploaded.filename:
        raise ValueError("No file uploaded.")

    filename = secure_filename(uploaded.filename)
    if not filename.lower().endswith(ALLOWED_EXCEL_EXTENSIONS):
        raise ValueError("Upload an .xlsx or .xlsm file.")

    stem = Path(filename).stem
    suffix = Path(filename).suffix
    # UUID suffixes keep simultaneous users from overwriting each other.
    saved_filename = f"{stem}_{uuid.uuid4().hex[:8]}{suffix}"
    destination = upload_dir / saved_filename
    uploaded.save(destination)
    return saved_filename, destination


def read_workbook_sheets(path: Path) -> list[str]:
    """Read sheet names without loading workbook cell data into memory."""
    workbook = openpyxl.load_workbook(path, read_only=True, data_only=False)
    try:
        return workbook.sheetnames
    finally:
        workbook.close()


def resolve_allowed_path(raw_path: str, app_root: Path, allowed_roots: list[Path]) -> Path:
    """Resolve a user-supplied path and ensure it stays inside allowed roots."""
    if not raw_path:
        raise ValueError("Path is required.")

    candidate = Path(raw_path)
    if not candidate.is_absolute():
        candidate = app_root / candidate

    resolved = candidate.resolve()
    allowed = [root.resolve() for root in allowed_roots]
    # Prevent download/apply endpoints from reading arbitrary server files.
    if not any(resolved == root or resolved.is_relative_to(root) for root in allowed):
        raise ValueError("Path is outside the application data directory.")

    return resolved