Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
35 changes: 29 additions & 6 deletions openrag/components/files.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
import asyncio
import re
import secrets
import time
from pathlib import Path
from typing import Dict, Optional

import aiofiles
from fastapi import UploadFile
import consts
import time
import secrets
import asyncio
from typing import Dict, Optional
from fastapi import UploadFile


def sanitize_filename(filename: str) -> str:
# Split filename into name and extension
path = Path(filename)
name = path.stem
ext = path.suffix

# Remove special characters (keep only word characters and hyphens temporarily)
name = re.sub(r"[^\w\-]", "_", name)

# Replace hyphens with underscores
name = name.replace("-", "_")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is hyphens really problematic ? Lot of files have it

@Ahmath-Gadji Ahmath-Gadji Dec 9, 2025 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn’t an issue during indexation, but it becomes one with Chainlit.

For example, the filename allocution_de_Mr_trump,__au_COP21_-_Hello_20251119065407.txt indexes correctly, but when displayed in the Chainlit UI, some underscores are interpreted as Markdown italics markers, breaking the file links.

  • No issue with filenames like:
    allocution_de_Mr_trump,__au_COP21__Hello_20251119065407.txt (only underscores)
    allocution_de_Mr_trump,-au-COP21-Hello-20251119065407.txt (only hyphens)

  • The issue happens specifically with:
    allocution_de_Mr_trump,__au_COP21_-_Hello_20251119065407.txt
    as shown in the screenshot.

image

This suggests that Chainlit doesn’t fail on _ or - individually, but on certain combinations of both.

To keep things simple, we can sanitize the filename for safe display and during indexation. In another file metadata we can store the original name, such as "original_filename".

@paultranvan paultranvan Dec 9, 2025 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's keep it like this then and add the original_filename


# Collapse multiple underscores
name = re.sub(r"_+", "_", name)

@paultranvan paultranvan Dec 9, 2025 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is is a problem to have multiple underscore? I know I have files with them, this case seems legit to me

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Remove special characters (keep only word characters and hyphens temporarily)
name = re.sub(r"[^\w\-]", "_", name)

With this code, if there are consecutive special characters, each one is replaced with “_”, which can result in filenames containing many underscores. I added that line for further cleaning, but having multiple underscores doesn’t cause any issues during indexing.


# Remove leading/trailing underscores
name = name.strip("_")

# Reconstruct filename
return name + ext


def make_unique_filename(filename: str) -> Path:
Expand Down Expand Up @@ -64,6 +88,5 @@ async def serialize_file(task_id: str, path: str, metadata: Optional[Dict] = {})
except Exception:
raise
else:

ray.cancel(future, recursive=True)
raise TimeoutError(f"Serialization task {task_id} timed out after seconds")
40 changes: 29 additions & 11 deletions openrag/components/test_files.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import io
import uuid
from pathlib import Path

from fastapi import UploadFile
import pytest

from components.files import save_file_to_disk
from components.files import sanitize_filename, save_file_to_disk
from fastapi import UploadFile


@pytest.mark.asyncio
Expand All @@ -18,11 +16,7 @@ async def test_save_file_to_disk_writes_content(tmp_path: Path):

dest_dir = tmp_path / "uploads"

saved_path = await save_file_to_disk(
file=upload,
dest_dir=dest_dir,
chunk_size=4
)
saved_path = await save_file_to_disk(file=upload, dest_dir=dest_dir, chunk_size=4)

assert saved_path.exists()
assert saved_path.parent == dest_dir
Expand All @@ -34,10 +28,8 @@ async def test_save_file_to_disk_writes_content(tmp_path: Path):
assert saved_content == content



@pytest.mark.asyncio
async def test_save_file_to_disk_with_random_prefix(tmp_path, monkeypatch):

def fake_make_unique_filename(filename: str) -> str:
assert filename == "test.txt"
return "PREFIX_1234_test.txt"
Expand All @@ -63,3 +55,29 @@ def fake_make_unique_filename(filename: str) -> str:
assert saved_path.name == "PREFIX_1234_test.txt"
assert saved_path.exists()
assert saved_path.read_bytes() == file_content


@pytest.mark.parametrize(
"input_name,expected",
[
# Basic cases
("simple_file.txt", "simple_file.txt"),
("file-name.pdf", "file_name.pdf"),
# Spaces and commas
("my file.txt", "my_file.txt"),
("file,name.txt", "file_name.txt"),
("multiple spaces.txt", "multiple_spaces.txt"),
# Special characters
("file@name#2024.txt", "file_name_2024.txt"),
("doc$with%special&chars.pdf", "doc_with_special_chars.pdf"),
# Multiple underscores
("file___name.txt", "file_name.txt"),
("file__name__here.txt", "file_name_here.txt"),
# Edge cases
("", ""),
("file(1).txt", "file_1.txt"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test with . in file name? 🙏

@Ahmath-Gadji Ahmath-Gadji Dec 9, 2025 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

("file.with.dot.txt", "file_with_dot.txt"),
],
)
def test_sanitize_filename(input_name, expected):
assert sanitize_filename(input_name) == expected
3 changes: 3 additions & 0 deletions openrag/routers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Any, Optional

import consts
from components.files import sanitize_filename
from config import load_config
from fastapi import Depends, Form, HTTPException, Request, UploadFile, status
from openai import AsyncOpenAI
Expand Down Expand Up @@ -230,6 +231,8 @@ async def validate_file_format(
status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
detail=details,
)

file.filename = sanitize_filename(file.filename)
return file


Expand Down
Loading