"""PDF -> PDF passthrough (.pdf uploads). A valid, unencrypted PDF is validated with pypdf and passed through to the output with the standard naming/collision contract, re-stamping /Title from the uploaded filename stem. It reuses common.finalize_pdf so the atomic write path (.tmp + os.replace) and metadata handling are identical to every other converter. Rejections (all ValueError -> surfaced verbatim as a job error / CLI error): - corrupt / unparseable -> "Invalid PDF: ... could not be parsed" - encrypted / password -> "PDF rejected: ... encrypted or password-protected" - more than MAX_PAGES -> "PDF too large: ... pages (max N)" """ from __future__ import annotations import io from pathlib import Path from pypdf import PdfReader from . import common # req 1 (pdf passthrough): upper bound on page count for a pass-through PDF. MAX_PAGES = 500 def convert(path: Path, job_id: str, progress_cb=None, output_dir=None, source_name=None) -> str: """Validate a PDF and pass it through to a same-named output PDF. Satisfies: req 1 (pdf passthrough — validate, stamp /Title, standard naming/collision, atomic write). """ name = source_name or path.name stem = Path(name).stem or "book" raw = path.read_bytes() # req 1: validate with pypdf. Construction raises on header/body corruption. try: reader = PdfReader(io.BytesIO(raw)) except Exception as exc: # noqa: BLE001 - pypdf raises many corrupt-file types raise ValueError(f"Invalid PDF: {name} could not be parsed ({exc})") from exc # req 1: reject encrypted / password-protected BEFORE touching .pages # (accessing pages on an encrypted reader raises, not a clean error). if reader.is_encrypted: raise ValueError(f"PDF rejected: {name} is encrypted or password-protected") # req 1: count pages; a corrupt body beyond a valid header fails here. try: page_count = len(reader.pages) except Exception as exc: # noqa: BLE001 raise ValueError(f"Invalid PDF: {name} could not be parsed ({exc})") from exc # req 1: reject oversized documents (clear error, distinct from corrupt). if page_count > MAX_PAGES: raise ValueError( f"PDF too large: {name} has {page_count} pages (max {MAX_PAGES})" ) # req 1: stamp /Title from the uploaded filename stem; standard naming/collision. common.report(progress_cb, 30.0, f"Passing through {name} (1/1)") out_dir = common.default_output_dir(output_dir) filename = common.derive_export_name(name, out_dir) return common.finalize_pdf(raw, out_dir, filename, job_id, progress_cb=progress_cb, title=stem)