From aac62a1335398d7172c63e9ab7e09c07dbca6211 Mon Sep 17 00:00:00 2001 From: sirdave73 Date: Fri, 4 Sep 2026 17:45:00 -0400 Subject: [PATCH] fix(office): sanitize LibreOffice failures + resolve binary via shutil.which --- converters/office_converter.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/converters/office_converter.py b/converters/office_converter.py index c0d90da..b0e56e0 100644 --- a/converters/office_converter.py +++ b/converters/office_converter.py @@ -13,6 +13,7 @@ soffice first-run lock hang). """ import logging import re +import shutil import subprocess from pathlib import Path @@ -33,17 +34,22 @@ def _run_office(src: Path, work: Path) -> bytes: Satisfies: spec render RICH_RENDER. """ work.mkdir(parents=True, exist_ok=True) + binary = shutil.which("libreoffice") or "/usr/bin/libreoffice" try: subprocess.run( - ["/usr/bin/libreoffice", "--headless", "--convert-to", "pdf", + [binary, "--headless", "--convert-to", "pdf", "--outdir", str(work), str(src)], check=True, timeout=LIBREOFFICE_TIMEOUT, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except FileNotFoundError as exc: - raise ValueError( - "LibreOffice is not installed. Install it to enable Rich mode." - ) from exc + raise ValueError("LibreOffice is not installed. Install it to enable Rich mode.") from exc + except subprocess.CalledProcessError as exc: + # Read the captured stderr (already on exc) into a clean, actionable message + detail = exc.stderr.decode(errors="replace").strip() or f"exit status {exc.returncode}" + raise ValueError(f"LibreOffice could not render the document ({detail})") from exc + except subprocess.TimeoutExpired as exc: + raise ValueError("LibreOffice timed out rendering the document (600s)") from exc # LibreOffice names its output after the ON-DISK input basename (e.g. the # web job path job-abc123.docx -> job-abc123.pdf), NOT the original upload # name, so locate the PDF there rather than by the source stem.