Repository: Quansight/ragna (python)

Issue:
[BUG] - 500 Internal Server Error Upon Ingestion of Certain Files
### Bug description

When ingesting files with filenames containing special characters, particularly '&' and '#', Ragna issues a 500 Internal Server Error via its API. This issue seems to be due to the improper URL encoding of these filenames in the commands that precede file uploading.

### How to reproduce the bug?

echo "Hello! This is a test." > "test&test.txt"

Try to start a new conversation in Ragna UI by upload the "test&test.txt" file.

Log:

INFO:     x.x.x.x:35076 - "OPTIONS /document?name=test&test.txt HTTP/1.1" 200 OK
INFO:     x.x.x.x:35076 - "GET /document?name=test&test.txt HTTP/1.1" 200 OK
INFO:     x.x.x.x:35076 - "POST /document HTTP/1.1" 500 Internal Server Error
INFO:     127.0.0.1:51872 - "POST /chats HTTP/1.1" 500 Internal Server Error

Notice how the '&' is not properly URL encoded in the calls leading up to the first POST. The same thing happens with the hash character (i.e., '#').

### Versions and dependencies used

Ragna v0.1.1

### Anything else?

_No response_

Pull request:
supply document name as part of the request body rather than query
Fixes #172. Now we need to PUT the document to upload it. I need to check if this works with S3 before we can merge.

Patch:
diff --git a/ragna/deploy/_api/core.py b/ragna/deploy/_api/core.py
index c4abe84..7f8dcf3 100644
--- a/ragna/deploy/_api/core.py
+++ b/ragna/deploy/_api/core.py
@@ -4,7 +4,7 @@ import uuid
 from typing import Annotated, Any, Iterator, Type, cast
 
 import aiofiles
-from fastapi import Depends, FastAPI, Form, HTTPException, Request, UploadFile
+from fastapi import Body, Depends, FastAPI, Form, HTTPException, Request, UploadFile
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import JSONResponse
 
@@ -112,10 +112,10 @@ def app(config: Config) -> FastAPI:
         with make_session() as session:  # type: ignore[attr-defined]
             yield session
 
-    @app.get("/document")
-    async def get_document_upload_info(
+    @app.post("/document")
+    async def create_document_upload_info(
         user: UserDependency,
-        name: str,
+        name: Annotated[str, Body(..., embed=True)],
     ) -> schemas.DocumentUploadInfo:
         with get_session() as session:
             document = schemas.Document(name=name)
@@ -127,7 +127,7 @@ def app(config: Config) -> FastAPI:
             )
             return schemas.DocumentUploadInfo(url=url, data=data, document=document)
 
-    @app.post("/document")
+    @app.put("/document")
     async def upload_document(
         token: Annotated[str, Form()], file: UploadFile
     ) -> schemas.Document:
diff --git a/ragna/deploy/_ui/resources/upload.js b/ragna/deploy/_ui/resources/upload.js
index 5d337af..71c0d9a 100644
--- a/ragna/deploy/_ui/resources/upload.js
+++ b/ragna/deploy/_ui/resources/upload.js
@@ -7,8 +7,10 @@ function upload(files, token, informationsEndpoint, final_callback) {
 }
 
 async function uploadFile(file, token, informationEndpoint) {
-  const response = await fetch(`${informationEndpoint}?name=${file.name}`, {
-    headers: { Authorization: `Bearer ${token}` },
+  const response = await fetch(informationEndpoint, {
+    method: "POST",
+    headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
+    body: JSON.stringify({ name: file.name }),
   });
   const documentInfo = await response.json();
 
@@ -19,7 +21,7 @@ async function uploadFile(file, token, informationEndpoint) {
   body.append("file", file);
 
   await fetch(documentInfo.url, {
-    method: "POST",
+    method: "PUT",
     body: body,
   });
 
diff --git a/scripts/add_chats.py b/scripts/add_chats.py
index 650ac5e..b77f933 100644
--- a/scripts/add_chats.py
+++ b/scripts/add_chats.py
@@ -35,9 +35,9 @@ def main():
     for i in range(5):
         name = f"document{i}.txt"
         document_info = (
-            client.get("/document", params={"name": name}).raise_for_status().json()
+            client.post("/document", json={"name": name}).raise_for_status().json()
         )
-        client.post(
+        client.put(
             document_info["url"],
             data=document_info["data"],
             files={"file": f"Content of {name}".encode()},


Test patch:
diff --git a/tests/deploy/api/test_e2e.py b/tests/deploy/api/test_e2e.py
index aa8d081..35dc6a1 100644
--- a/tests/deploy/api/test_e2e.py
+++ b/tests/deploy/api/test_e2e.py
@@ -50,7 +50,7 @@ def check_api(config):
         assert client.get("/chats").raise_for_status().json() == []
 
         document_info = (
-            client.get("/document", params={"name": document_path.name})
+            client.post("/document", json={"name": document_path.name})
             .raise_for_status()
             .json()
         )
@@ -58,7 +58,7 @@ def check_api(config):
         assert document["name"] == document_path.name
 
         with open(document_path, "rb") as file:
-            client.post(
+            client.put(
                 document_info["url"],
                 data=document_info["data"],
                 files={"file": file},