Skip to content
Open
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
21 changes: 21 additions & 0 deletions prepline_general/api/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import mimetypes
import os
import secrets
import zipfile
from base64 import b64encode
from concurrent.futures import ThreadPoolExecutor
from functools import partial
Expand All @@ -26,10 +27,12 @@
status,
)
from fastapi.responses import PlainTextResponse, StreamingResponse
from olefile.olefile import OleFileError # note: olefile <0.48 does not re-export it at top level
from pypdf import PageObject, PdfReader, PdfWriter
from pypdf.errors import FileNotDecryptedError, PdfReadError
from starlette.datastructures import Headers
from starlette.types import Send
from unstructured.errors import UnprocessableEntityError

from prepline_general.api import __version__ as api_version
from prepline_general.api.filetypes import get_validated_mimetype
Expand Down Expand Up @@ -397,6 +400,24 @@ def pipeline_api(
else:
elements = partition(**partition_kwargs) # type: ignore # pyright: ignore[reportGeneralTypeIssues]

except OleFileError as e:
# OleFileError subclasses OSError, so it must be caught before the OSError
# handler below. Corrupt or truncated OLE files (.doc, .ppt, .msg) are a
# client-side problem and should not surface as a 500.
raise HTTPException(
status_code=400,
detail=f"File does not appear to be a valid OLE file: {e}",
)
except zipfile.BadZipFile as e:
raise HTTPException(
status_code=400,
detail=f"File does not appear to be a valid OOXML file: {e}",
)
except UnprocessableEntityError as e:
raise HTTPException(
status_code=400,
detail=str(e),
)
except OSError as e:
if isinstance(e.args[0], str) and (
"chipper-fast-fine-tuning is not a local folder" in e.args[0]
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ dependencies = [
"psutil >=7.0.0, <8.0.0",
"pypdf >=6.0.0, <7.0.0",
"requests >=2.32.0, <3.0.0",
# imported directly for OleFileError; also a transitive dep of unstructured
"olefile >=0.47, <1.0.0",
]

[dependency-groups]
Expand Down
28 changes: 28 additions & 0 deletions test_general/api/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,34 @@ def test_general_api(example_filename, content_type):
assert len(dfs) > 0


@pytest.mark.parametrize(
"example_filename, content_type",
[
("fake.doc", "application/msword"),
(
"fake-power-point.pptx",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
),
],
)
def test_truncated_binary_documents_return_400(example_filename, content_type):
"""
A truncated binary document is a client-side problem and must return 400,
not the catch-all 500. Truncating a valid fixture mid-file corrupts the
OLE/ZIP container without changing its detected file type.
"""
client = TestClient(app)
test_file_path = Path("sample-docs") / example_filename
with open(test_file_path, "rb") as f:
data = f.read()
response = client.post(
MAIN_API_ROUTE,
files=[("files", (example_filename, io.BytesIO(data[: len(data) // 2]), content_type))],
)

assert response.status_code == 400


def test_metadata_fields_removed():
"""
Verify that responses do not include coordinates unless requested
Expand Down
2 changes: 2 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.