From cc767dcf46cd50c5a7fd35921268c8da7a6148cd Mon Sep 17 00:00:00 2001 From: RGalyaviev Date: Thu, 4 Sep 2025 09:07:16 +0300 Subject: [PATCH] Exports: use Flask send_file with ASCII-safe filenames to avoid Latin-1 header errors; add slugified download_name --- app.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/app.py b/app.py index 0a2b03e..e8bbc02 100644 --- a/app.py +++ b/app.py @@ -14,9 +14,12 @@ from flask import ( abort, flash, Response, + send_file, ) from io import BytesIO import re +import unicodedata +from urllib.parse import quote as urlquote from xhtml2pdf import pisa # type: ignore from docx import Document # type: ignore from htmldocx import HtmlToDocx # type: ignore @@ -284,17 +287,24 @@ def create_app(): html + "" ) + def _safe_download_name(title: str, uid: str, ext: str) -> str: + base = (title or f"page-{uid[:8]}").strip() + ascii_base = unicodedata.normalize("NFKD", base).encode("ascii", "ignore").decode("ascii") + ascii_base = re.sub(r"[^A-Za-z0-9_.-]+", "_", ascii_base).strip("_") or f"page-{uid[:8]}" + return f"{ascii_base}.{ext}" + @app.route("/p/.pdf") def export_pdf(uid: str): row = _fetch_page(uid) title = row["title"] or f"page-{uid[:8]}" - cleaned = _sanitize_html_for_pdf(row["html"]) + cleaned = _sanitize_html_for_pdf(row["html"]) html_doc = _wrap_html_for_export(title, cleaned) out = BytesIO() pisa.CreatePDF(src=html_doc, dest=out) out.seek(0) - headers = {"Content-Disposition": f"attachment; filename={title}.pdf"} - return Response(out.read(), mimetype="application/pdf", headers=headers) + filename = _safe_download_name(title, uid, "pdf") + # send_file properly sets Content-Disposition with download_name + return send_file(out, mimetype="application/pdf", as_attachment=True, download_name=filename) @app.route("/p/.docx") def export_docx(uid: str): @@ -306,8 +316,13 @@ def create_app(): out = BytesIO() doc.save(out) out.seek(0) - headers = {"Content-Disposition": f"attachment; filename={title}.docx"} - return Response(out.read(), mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document", headers=headers) + filename = _safe_download_name(title, uid, "docx") + return send_file( + out, + mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document", + as_attachment=True, + download_name=filename, + ) # Optional: simple 404 page to keep things minimal @app.errorhandler(404)