Repository navigation
fix(files): download files whose names are not ASCII - #2426
Merged
jaluma merged 2 commits intoNov 10, 2026
Merged
Conversation
GET /v1/files/{file_id}/content wrote the stored file name straight into
`Content-Disposition: attachment; filename="..."`. Starlette encodes header
values as Latin-1, so a name with any character outside Latin-1 (CJK,
Cyrillic, emoji, an en dash, macOS's U+202F in screenshot names) raised
UnicodeEncodeError and the download failed with a 500, and a Latin-1 name
such as résumé.csv went out as raw Latin-1 bytes that clients decode
inconsistently.
Keep the header byte for byte for ASCII names and send any other name as
RFC 6266 `filename*=UTF-8''<percent-encoded>`, as Starlette's FileResponse
does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Contributor
|
Stale pull request |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
GET /v1/files/{file_id}/contentwrites the file name straight intoContent-Disposition: attachment; filename="..."(private_gpt/server/files/files_router.py:307). Starlette encodes response header values as Latin-1 (Response.init_headers), which causes two problems:UnicodeEncodeErrorand the download fails with a 500. That covers Chinese, Cyrillic, Greek and emoji, and also everyday punctuation: an en dash, a curly apostrophe, or the U+202F narrow no-break space that macOS puts before AM/PM in screenshot names.résumé.csvgoes out as raw Latin-1 bytes. RFC 6266 advises against non-ASCII characters infilenamebecause clients decode them inconsistently, and Starlette's ownTestClientcannot decode that header at all.Uploading accepts these names, so the file is stored and listed normally. Only the download breaks. The
/contentendpoint serves the workbench UI's download links and inline image previews (sessionFileUrlinui/index.html) and the Anthropic SDK'sclient.beta.files.download().With this change, ASCII names keep the current header byte for byte. Any other name is sent as
filename*=UTF-8''<percent-encoded name>(RFC 6266 / RFC 8187), which is the form Starlette'sFileResponseuses and which current browsers read.HEAD /v1/files/{file_id}copies the same name into its customX-File-Filenameheader and fails the same way for names outside Latin-1. Custom headers have no standard encoding, so I left HEAD out of this PR. I can add an encoding for it if you have one in mind.Type of Change
How Has This Been Tested?
test_content_download_filename_headerintests/server/files/test_files_router.pyuploads a file under three names, downloads each through/content, and checks the exactContent-Dispositionvalue:data.csv(unchanged),résumé.csvand报告.csv.On
main, with only the test file changed:[latin-1]fails withUnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 23while the test client reads the header.[non-latin-1]fails withassert 500 == 200. The 500 comes fromUnicodeEncodeError: 'latin-1' codec can't encode characters in position 22-23, raised in Starlette'sResponse.init_headers.With the fix, the new cases pass:
The one failure,
test_head_file_returns_metadata_headers, fails the same way without this change in my environment: the libmagic build I had to use detects the uploaded text file asapplication/octet-stream. It is unrelated to this PR.The full
pytest testsrun doesn't pass cleanly in my sandbox, which blocks NLTK data downloads among other things: 336 tests fail onmainwith only the test file changed, and 334 with this change. The difference is exactly the two newContent-Dispositioncases, so nothing else changes. CI is the real check for the rest of the suite.Test Configuration:
Checklist:
make check; make testto ensure type checks and tests pass (make checkpasses;make testsee above)🤖 Generated with Claude Code