-
Notifications
You must be signed in to change notification settings - Fork 860
Description
Describe the bug
Description:
When attempting to export a notebook as HTML via the web UI or API, the application crashes if the source filename contains non-ASCII characters (such as Chinese characters, Emojis, etc.).
This happens because the filename is passed directly to the Content-Disposition header, and the underlying ASGI server (Starlette/Uvicorn) attempts to encode the header values using latin-1, which fails for non-ASCII strings.
Steps to Reproduce:
- Create a marimo notebook with a Chinese name, e.g.,
数据分析.py. - Run
marimo edit 数据分析.py. - Open the browser menu and click "Download as HTML".
- The backend throws a
UnicodeEncodeErrorand the download fails.
Error Traceback:
File "/site-packages/marimo/_server/api/endpoints/export.py", line 89, in export_as_html
return HTMLResponse(
File "/site-packages/starlette/responses.py", line 47, in __init__
self.init_headers(headers)
File "/site-packages/starlette/responses.py", line 62, in init_headers
raw_headers = [(k.lower().encode("latin-1"), v.encode("latin-1")) for k, v in headers.items()]
UnicodeEncodeError: 'latin-1' codec can't encode characters in position XX: ordinal not in range(256)
Environment:
- OS: macOS / Linux / Windows
- Python Version: 3.10
- Marimo Version: 0.18.4
Root Cause Analysis:
The export_as_html endpoint sets the Content-Disposition header using the raw filename:
headers={"Content-Disposition": f'attachment; filename="{filename}"'}According to HTTP standards (and enforced by Starlette), header values must be ISO-8859-1 (Latin-1) compatible.
below is fix by Gemini-3
Suggested Fix:
We should handle non-ASCII filenames by either:
- URL-encoding the filename (percent-encoding).
- Or simply using a generic fallback name (e.g.,
download.html) if encoding fails. - Or properly implementing RFC 5987 (
filename*=UTF-8''...).
Code Change Proposal:
In marimo/_server/api/endpoints/export.py, modify the export_as_html function:
# Import quote to handle special characters
from urllib.parse import quote
# ... inside the function ...
# Encode the filename to be safe for HTTP headers
safe_filename = quote(filename)
return HTMLResponse(
content=html,
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{safe_filename}"},
)Alternative Quick Fix (Sanitization):
If full RFC 5987 support is too complex, we can strip non-ASCII characters or use a default name:
try:
filename.encode('latin-1')
except UnicodeEncodeError:
# Fallback for non-ASCII filenames to prevent crash
filename = "download.html"
return HTMLResponse(
content=html,
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)Will you submit a PR?
- Yes
Environment
Details
{
"marimo": "0.18.4",
"editable": false,
"location": "~/miniconda3/lib/python3.10/site-packages/marimo",
"OS": "Darwin",
"OS Version": "24.5.0",
"Processor": "arm",
"Python Version": "3.10.8",
"Locale": "en_US",
"Binaries": {
"Browser": "143.0.7499.170",
"Node": "v24.3.0"
},
"Dependencies": {
"click": "8.3.1",
"docutils": "0.22.4",
"itsdangerous": "2.2.0",
"jedi": "0.19.2",
"markdown": "3.10",
"narwhals": "2.14.0",
"packaging": "25.0",
"psutil": "7.0.0",
"pygments": "2.19.1",
"pymdown-extensions": "10.20",
"pyyaml": "6.0.3",
"starlette": "0.50.0",
"tomlkit": "0.13.3",
"typing-extensions": "4.15.0",
"uvicorn": "0.34.3",
"websockets": "15.0.1"
},
"Optional Dependencies": {
"duckdb": "1.3.0",
"mcp": "1.25.0",
"nbformat": "5.10.4",
"openai": "2.14.0",
"pandas": "2.2.3",
"polars": "1.30.0",
"pyarrow": "20.0.0",
"sqlglot": "26.25.3"
},
"Experimental Flags": {}
}
Code to reproduce
No response