generated from john/python-template
This commit is contained in:
@@ -4,10 +4,10 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
from urllib.parse import unquote
|
||||
|
||||
_ABSOLUTE_SCHEMES = ("http://", "https://", "data:")
|
||||
_UPLOAD_ROUTE_PREFIX = "/uploads/"
|
||||
_CANONICAL_PREFIXES = ("documents/", "persons/")
|
||||
|
||||
|
||||
def absolute_upload_url(path: str, *, base_url: str) -> str:
|
||||
@@ -18,13 +18,7 @@ def absolute_upload_url(path: str, *, base_url: str) -> str:
|
||||
|
||||
|
||||
def resolve_media_url(path: str | None, *, upload_dir: Path, base_url: str) -> str | None:
|
||||
"""Map a stored media path onto a served upload URL.
|
||||
|
||||
Stored paths have accumulated several shapes over the life of the schema:
|
||||
absolute filesystem paths, paths relative to the working directory, paths
|
||||
relative to the upload root, and paths that already carry an upload route.
|
||||
All of them must still resolve, so each shape is tried in turn.
|
||||
"""
|
||||
"""Map a canonical stored relative media path onto a served upload URL."""
|
||||
candidate = (path or "").strip()
|
||||
if not candidate:
|
||||
return None
|
||||
@@ -34,56 +28,23 @@ def resolve_media_url(path: str | None, *, upload_dir: Path, base_url: str) -> s
|
||||
if lowered.startswith(_ABSOLUTE_SCHEMES):
|
||||
return normalized
|
||||
if normalized.startswith(_UPLOAD_ROUTE_PREFIX):
|
||||
upload_relative = normalized.removeprefix(_UPLOAD_ROUTE_PREFIX)
|
||||
return _resolve_upload_relative(upload_relative, upload_dir=upload_dir, base_url=base_url)
|
||||
|
||||
resolved_upload_dir = upload_dir.resolve()
|
||||
path_obj = Path(candidate)
|
||||
|
||||
if path_obj.is_absolute():
|
||||
absolute_candidates = [path_obj.resolve()]
|
||||
else:
|
||||
absolute_candidates = [
|
||||
(Path.cwd() / path_obj).resolve(),
|
||||
(resolved_upload_dir / path_obj).resolve(),
|
||||
]
|
||||
|
||||
for absolute_candidate in absolute_candidates:
|
||||
try:
|
||||
relative = absolute_candidate.relative_to(resolved_upload_dir).as_posix()
|
||||
except ValueError:
|
||||
continue
|
||||
if absolute_candidate.is_file():
|
||||
return absolute_upload_url(f"/uploads/{quote(relative)}", base_url=base_url)
|
||||
|
||||
upload_name = resolved_upload_dir.name.casefold()
|
||||
normalized_parts = Path(normalized).parts
|
||||
lowered_parts = [part.casefold() for part in normalized_parts]
|
||||
if upload_name in lowered_parts:
|
||||
index = lowered_parts.index(upload_name)
|
||||
relative = Path(*normalized_parts[index + 1 :]).as_posix()
|
||||
if relative:
|
||||
return _resolve_upload_relative(relative, upload_dir=upload_dir, base_url=base_url)
|
||||
|
||||
if lowered.startswith("uploads/"):
|
||||
upload_relative = normalized.split("/", 1)[1] if "/" in normalized else ""
|
||||
return _resolve_upload_relative(upload_relative, upload_dir=upload_dir, base_url=base_url)
|
||||
if lowered.startswith("data/"):
|
||||
relative = normalized.split("/", 1)[1] if "/" in normalized else ""
|
||||
return _resolve_upload_relative(relative, upload_dir=upload_dir, base_url=base_url)
|
||||
if lowered.startswith(("documents/", "persons/")):
|
||||
return _resolve_upload_relative(normalized.removeprefix(_UPLOAD_ROUTE_PREFIX), upload_dir=upload_dir, base_url=base_url)
|
||||
if lowered.startswith(_CANONICAL_PREFIXES):
|
||||
return _resolve_upload_relative(normalized, upload_dir=upload_dir, base_url=base_url)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_upload_relative(relative: str, *, upload_dir: Path, base_url: str) -> str | None:
|
||||
candidate = unquote(relative).strip().replace("\\", "/")
|
||||
candidate = relative.strip().replace("\\", "/")
|
||||
if not candidate:
|
||||
return None
|
||||
resolved_upload_dir = upload_dir.resolve()
|
||||
try:
|
||||
absolute_candidate = (resolved_upload_dir / Path(candidate)).resolve()
|
||||
normalized_relative = Path(candidate).as_posix()
|
||||
if not normalized_relative.casefold().startswith(_CANONICAL_PREFIXES):
|
||||
return None
|
||||
absolute_candidate = (resolved_upload_dir / Path(normalized_relative)).resolve()
|
||||
safe_relative = absolute_candidate.relative_to(resolved_upload_dir).as_posix()
|
||||
except ValueError:
|
||||
return None
|
||||
@@ -94,6 +55,7 @@ def _resolve_upload_relative(relative: str, *, upload_dir: Path, base_url: str)
|
||||
|
||||
def public_media_path_label(path: str | None, *, upload_dir: Path) -> str:
|
||||
"""Return a safe, non-local path label for UI metadata display."""
|
||||
_ = upload_dir
|
||||
candidate = (path or "").strip()
|
||||
if not candidate:
|
||||
return "unknown"
|
||||
@@ -103,23 +65,6 @@ def public_media_path_label(path: str | None, *, upload_dir: Path) -> str:
|
||||
|
||||
if normalized.startswith(_UPLOAD_ROUTE_PREFIX):
|
||||
return normalized
|
||||
if lowered.startswith("uploads/"):
|
||||
return f"/{normalized}"
|
||||
if lowered.startswith("data/"):
|
||||
relative = normalized.split("/", 1)[1] if "/" in normalized else ""
|
||||
return f"/uploads/{relative}" if relative else "/uploads"
|
||||
if lowered.startswith(("documents/", "persons/")):
|
||||
if lowered.startswith(_CANONICAL_PREFIXES):
|
||||
return f"/uploads/{normalized}"
|
||||
|
||||
resolved_upload_dir = upload_dir.resolve()
|
||||
path_obj = Path(candidate)
|
||||
if path_obj.is_absolute():
|
||||
absolute_candidate = path_obj.resolve()
|
||||
try:
|
||||
relative = absolute_candidate.relative_to(resolved_upload_dir).as_posix()
|
||||
return f"/uploads/{relative}"
|
||||
except ValueError:
|
||||
# Never expose non-managed absolute filesystem paths.
|
||||
return path_obj.name or "unknown"
|
||||
|
||||
return f"/uploads/{quote(path_obj.name)}"
|
||||
return Path(normalized).name or "unknown"
|
||||
|
||||
Reference in New Issue
Block a user