Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/api/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,18 @@ async def upload_file(
# Read file content
content = await file.read()

# Store file directly
# Sanitize filename before storage so the name on disk in the
# execution pod matches what LibreChat reports to the model.
sanitized_name = OutputProcessor.sanitize_filename(file.filename)

# Store file with the sanitized name
file_id = await file_service.store_uploaded_file(
session_id=session_id,
filename=file.filename,
filename=sanitized_name,
content=content,
content_type=file.content_type,
)

# Sanitize filename to match what will be used in container
sanitized_name = OutputProcessor.sanitize_filename(file.filename)

uploaded_files.append(
{
"id": file_id,
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/test_api_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,42 @@ async def test_upload_single_file(self, mock_file_service, mock_session_service,
mock_file_service.store_uploaded_file.assert_called_once()
mock_session_service.create_session.assert_called_once()

@pytest.mark.asyncio
@pytest.mark.parametrize(
"original, expected",
[
("my file.csv", "my_file.csv"),
("report (final).xlsx", "report__final_.xlsx"),
("data&summary#2.txt", "data_summary_2.txt"),
("résumé (v2).pdf", "r_sum___v2_.pdf"),
("hello world!@#$.csv", "hello_world____.csv"),
],
)
async def test_upload_sanitizes_filename_before_storage(
self, mock_file_service, mock_session_service, original, expected
):
"""Test that filenames with special characters are sanitized before storing."""
file = MagicMock(spec=UploadFile)
file.filename = original
file.content_type = "text/csv"
file.size = 100
file.read = AsyncMock(return_value=b"a,b,c")

result = await upload_file(
file=file,
files=None,
entity_id=None,
file_service=mock_file_service,
session_service=mock_session_service,
)

# The stored filename should be sanitized
call_kwargs = mock_file_service.store_uploaded_file.call_args
assert call_kwargs.kwargs["filename"] == expected

# The response should also use the sanitized name
assert result["files"][0]["filename"] == expected

@pytest.mark.asyncio
async def test_upload_multiple_files(self, mock_file_service, mock_session_service, mock_upload_file):
"""Test uploading multiple files."""
Expand Down