laster 0.0.2

This commit is contained in:
2026-05-26 15:43:56 +07:00
parent e2c4881bb7
commit 8ceb1bb1df
24 changed files with 583 additions and 40 deletions

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
from email.message import Message
import re
from pathlib import Path
from urllib.parse import unquote, urlparse
@@ -14,10 +15,39 @@ from app.utils.validators import validate_url_host
SAFE_FILE_RE = re.compile(r"[^a-zA-Z0-9._+-]+")
def _safe_file_name(url: str) -> str:
def _sanitize_file_name(value: str, fallback: str = "package.deb") -> str:
name = SAFE_FILE_RE.sub("-", value).strip("-.")
return name or fallback
def _safe_url_file_name(url: str) -> str:
parsed = urlparse(url)
name = Path(unquote(parsed.path)).name or "package.deb"
return SAFE_FILE_RE.sub("-", name).strip("-") or "package.deb"
if name == "download":
parts = [part for part in parsed.path.split("/") if part]
name = "-".join(parts[-3:]) if len(parts) >= 3 else name
return _sanitize_file_name(name)
def _content_disposition_file_name(value: str) -> str:
if not value:
return ""
message = Message()
message["content-disposition"] = value
return _sanitize_file_name(message.get_filename() or "", fallback="")
def _response_file_name(url: str, response: httpx.Response) -> str:
name = _content_disposition_file_name(response.headers.get("content-disposition", ""))
if not name:
name = _safe_url_file_name(str(response.url) or url)
content_type = response.headers.get("content-type", "").split(";", 1)[0].strip().lower()
if content_type == "application/vnd.debian.binary-package" and not name.lower().endswith(".deb"):
name = f"{name}.deb"
return name
class Downloader:
@@ -28,12 +58,12 @@ class Downloader:
def download(self, url: str) -> Path:
validate_url_host(url, settings.allowed_download_hosts)
settings.cache_dir.mkdir(parents=True, exist_ok=True)
destination = settings.cache_dir / _safe_file_name(url)
self.repository.add_log(self.task_id, "info", f"Downloading {url}")
with httpx.stream("GET", url, follow_redirects=True, timeout=120) as response:
response.raise_for_status()
self._validate_response(url, response)
destination = settings.cache_dir / _response_file_name(url, response)
with destination.open("wb") as handle:
for chunk in response.iter_bytes():
handle.write(chunk)