Skip to content

Add MIME-type validation to document uploads#77

Merged
mattknatt merged 11 commits into
mainfrom
feat/MIME-validation
Apr 23, 2026
Merged

Add MIME-type validation to document uploads#77
mattknatt merged 11 commits into
mainfrom
feat/MIME-validation

Conversation

@mattknatt

@mattknatt mattknatt commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

Closes #78

Summary by CodeRabbit

  • New Features

    • Stronger document upload validation: 10 MB size limit, extension allowlist, and server-side MIME/content verification for safer, more reliable uploads.
    • Uploads now stream validated bytes to storage to ensure consistency.
  • Tests

    • Integration and service tests updated to use realistic PDF fixtures.
  • Chores

    • Added a content-detection library to support MIME verification.

@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@mattknatt has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 30 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 21 minutes and 30 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e6099ea-6819-4d5c-8379-d0cbf2f02897

📥 Commits

Reviewing files that changed from the base of the PR and between edf2bcc and e42687a.

⛔ Files ignored due to path filters (1)
  • src/test/resources/test.docx is excluded by !**/*.docx
📒 Files selected for processing (3)
  • src/main/java/org/example/projektarendehantering/application/service/DocumentService.java
  • src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java
  • src/test/java/org/example/projektarendehantering/application/service/DocumentServiceTest.java
📝 Walkthrough

Walkthrough

DocumentService.uploadDocument(...) gained strict upload validation: max size enforcement (10 MB), extension allowlist mapped to expected MIME types, multipart content-type verification, and server-side MIME detection via Apache Tika before uploading bytes (now uploaded from an in-memory ByteArrayInputStream). Tests updated to use PDF fixtures and pom.xml adds tika-core.

Changes

Cohort / File(s) Summary
File Upload Logic
src/main/java/org/example/projektarendehantering/application/service/DocumentService.java
Enforced max file size (10 MB); derive extension from originalFilename; map allowed extensions → expected MIME; validate multipart contentType against expected MIME; detect MIME via Apache Tika on fileBytes; upload to S3 from a ByteArrayInputStream of fileBytes instead of using file.getInputStream().
Tests — fixtures updated
src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java, src/test/java/org/example/projektarendehantering/application/service/DocumentServiceTest.java
Replaced test.txt/text/plain fixtures with test.pdf/application/pdf; adjusted mocked DTOs and assertions to match new filename/content-type.
Build — dependency added
pom.xml
Added org.apache.tika:tika-core:3.1.0 dependency to enable server-side MIME detection.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Service as DocumentService
    participant Tika as Apache Tika
    participant S3 as S3

    Client->>Service: POST multipart file (filename, contentType, bytes)
    Service->>Service: check file size <= 10MB
    Service->>Service: derive extension from filename
    Service->>Service: lookup expected MIME for extension
    Service->>Service: compare multipart contentType with expected MIME
    Service->>Tika: TIKA.detect(fileBytes)
    Tika-->>Service: detected MIME
    Service->>Service: compare detected MIME with expected MIME
    Service->>S3: upload ByteArrayInputStream(fileBytes)
    S3-->>Service: upload result
    Service-->>Client: response (created / error)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I hopped through bytes and checked each file,
Size and type made my whiskers smile,
Tika sniffed bytes, S3 got the stream,
PDFs pass now — what a dream! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add MIME-type validation to document uploads' accurately and concisely summarizes the primary change in the PR: introducing MIME-type validation to the document upload service.
Linked Issues check ✅ Passed The PR title and changes align with the linked issue #78 'Meta validation on files' by implementing MIME-type validation through extension allowlisting, content-type matching, and Tika-based file inspection.
Out of Scope Changes check ✅ Passed All changes are in-scope: MIME-type validation logic in DocumentService, updated test fixtures to use PDF files, and addition of Apache Tika dependency for MIME detection.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/MIME-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java (1)

94-109: ⚠️ Potential issue | 🟡 Minor

Use a PDF-like fixture instead of labeling "hello" as a PDF.

These integration fixtures now satisfy the new checks only because content type and extension are client-provided. Use a minimal PDF payload or fixture file so the test remains valid if MIME detection starts inspecting bytes.

🧪 Example fixture adjustment
+        byte[] pdfBytes = "%PDF-1.4\n%%EOF\n".getBytes(java.nio.charset.StandardCharsets.US_ASCII);
-        MockMultipartFile file = new MockMultipartFile("file", "test.pdf", "application/pdf", "hello".getBytes());
+        MockMultipartFile file = new MockMultipartFile("file", "test.pdf", "application/pdf", pdfBytes);

Apply the same fixture pattern to the unauthorized upload test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java`
around lines 94 - 109, The test uses MockMultipartFile with plain "hello" bytes
but labels it as a PDF, so change the fixture to a minimal valid PDF payload or
load a small PDF test resource instead of the "hello" string; update the
MockMultipartFile instantiation(s) in the test method(s) (e.g., the first upload
test and uploadDocument_shouldDenyUnauthorized) to pass a byte[] or InputStream
containing a minimal PDF header/content (or read from src/test/resources
fixture) while keeping the same filename "test.pdf" and contentType
"application/pdf" so the MIME/extension checks will still pass and the rest of
the assertions (documentRepository checks and verify(s3Template).upload(...))
remain unchanged.
🧹 Nitpick comments (1)
src/test/java/org/example/projektarendehantering/application/service/DocumentServiceTest.java (1)

72-116: Add negative tests for the new validation rules.

This PR adds rejection paths, but the unit tests only update existing valid fixtures. Please cover at least oversized files, disallowed extensions, disallowed content types, and extension/content-type mismatches.

🧪 Suggested test cases
`@Test`
void uploadDocument_shouldRejectDisallowedExtension() {
    MockMultipartFile file = new MockMultipartFile("file", "test.exe", "application/pdf", "hello".getBytes());

    assertThatThrownBy(() -> documentService.uploadDocument(doctorActor, caseId, file))
            .isInstanceOf(BadRequestException.class);
}

`@Test`
void uploadDocument_shouldRejectDisallowedContentType() {
    MockMultipartFile file = new MockMultipartFile("file", "test.pdf", "text/plain", "hello".getBytes());

    assertThatThrownBy(() -> documentService.uploadDocument(doctorActor, caseId, file))
            .isInstanceOf(BadRequestException.class);
}

`@Test`
void uploadDocument_shouldRejectMismatchedExtensionAndContentType() {
    MockMultipartFile file = new MockMultipartFile("file", "test.pdf", "image/png", "hello".getBytes());

    assertThatThrownBy(() -> documentService.uploadDocument(doctorActor, caseId, file))
            .isInstanceOf(BadRequestException.class);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/example/projektarendehantering/application/service/DocumentServiceTest.java`
around lines 72 - 116, The tests lack negative coverage for the new upload
validation in DocumentService.uploadDocument; add unit tests in
DocumentServiceTest that create MockMultipartFile inputs to assert
BadRequestException is thrown for (1) oversized file (create a byte[] larger
than the allowed max), (2) disallowed extension (e.g., "test.exe" with a PDF
content type), (3) disallowed content type (e.g., "test.pdf" with "text/plain"),
and (4) mismatched extension/content-type (e.g., "test.pdf" with "image/png");
use doctorActor and caseId fixtures, stub caseRepository.findById(caseId) to
return Optional.of(caseEntity) as in other tests, and assertThatThrownBy(() ->
documentService.uploadDocument(...)).isInstanceOf(BadRequestException.class) for
each case.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/org/example/projektarendehantering/application/service/DocumentService.java`:
- Around line 66-74: The extension and MIME checks in DocumentService are
independent, allowing spoofed uploads; replace them with a correlated mapping
(e.g., Map<String, Set<String>> extensionToMime) and validate that the uploaded
file's extension (derived from originalFilename) maps to the provided
contentType before accepting the file (use the same symbols
ALLOWED_EXTENSIONS/ALLOWED_CONTENT_TYPES to migrate into extensionToMime and
validate in the method that handles file upload). Additionally, for stronger
server-side validation, detect the actual MIME from file.getInputStream() (e.g.,
via Apache Tika or similar) and compare that detected type against the
extensionToMime entry to reject mismatches.

---

Outside diff comments:
In
`@src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java`:
- Around line 94-109: The test uses MockMultipartFile with plain "hello" bytes
but labels it as a PDF, so change the fixture to a minimal valid PDF payload or
load a small PDF test resource instead of the "hello" string; update the
MockMultipartFile instantiation(s) in the test method(s) (e.g., the first upload
test and uploadDocument_shouldDenyUnauthorized) to pass a byte[] or InputStream
containing a minimal PDF header/content (or read from src/test/resources
fixture) while keeping the same filename "test.pdf" and contentType
"application/pdf" so the MIME/extension checks will still pass and the rest of
the assertions (documentRepository checks and verify(s3Template).upload(...))
remain unchanged.

---

Nitpick comments:
In
`@src/test/java/org/example/projektarendehantering/application/service/DocumentServiceTest.java`:
- Around line 72-116: The tests lack negative coverage for the new upload
validation in DocumentService.uploadDocument; add unit tests in
DocumentServiceTest that create MockMultipartFile inputs to assert
BadRequestException is thrown for (1) oversized file (create a byte[] larger
than the allowed max), (2) disallowed extension (e.g., "test.exe" with a PDF
content type), (3) disallowed content type (e.g., "test.pdf" with "text/plain"),
and (4) mismatched extension/content-type (e.g., "test.pdf" with "image/png");
use doctorActor and caseId fixtures, stub caseRepository.findById(caseId) to
return Optional.of(caseEntity) as in other tests, and assertThatThrownBy(() ->
documentService.uploadDocument(...)).isInstanceOf(BadRequestException.class) for
each case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 92021ae0-996a-4736-8697-433634cdebb7

📥 Commits

Reviewing files that changed from the base of the PR and between 318c675 and 0db0b5f.

📒 Files selected for processing (3)
  • src/main/java/org/example/projektarendehantering/application/service/DocumentService.java
  • src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java
  • src/test/java/org/example/projektarendehantering/application/service/DocumentServiceTest.java

@mattknatt

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/org/example/projektarendehantering/application/service/DocumentService.java (1)

70-79: Use Locale.ROOT and collapse the two identical-message branches.

toLowerCase() without a Locale is locale-sensitive (Turkish I/ı is the classic trap) — not currently exploitable since the map keys are ASCII, but the prior review already suggested Locale.ROOT; worth applying here for consistency. The two throw branches at lines 74-76 and 77-79 produce the exact same error message and can be merged.

♻️ Optional refactor
         String extension = originalFilename.contains(".")
-                ? originalFilename.substring(originalFilename.lastIndexOf('.') + 1).toLowerCase()
+                ? originalFilename.substring(originalFilename.lastIndexOf('.') + 1).toLowerCase(java.util.Locale.ROOT)
                 : "";
         String expectedMime = EXTENSION_TO_MIME.get(extension);
-        if (expectedMime == null) {
-            throw new BadRequestException("File type not allowed. Allowed types: pdf, png, jpg, jpeg, docx");
-        }
-        if (!expectedMime.equalsIgnoreCase(contentType)) {
+        if (expectedMime == null || !expectedMime.equalsIgnoreCase(contentType)) {
             throw new BadRequestException("File type not allowed. Allowed types: pdf, png, jpg, jpeg, docx");
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/projektarendehantering/application/service/DocumentService.java`
around lines 70 - 79, In DocumentService.java, make the filename-to-extension
normalization use Locale.ROOT (e.g., call toLowerCase(Locale.ROOT) on the
substring derived from originalFilename) and collapse the two identical
BadRequestException branches into one check: look up expectedMime from
EXTENSION_TO_MIME by the normalized extension and if expectedMime is null or not
equalIgnoreCase to contentType, throw the single BadRequestException with the
existing message; update references to originalFilename, EXTENSION_TO_MIME, and
contentType accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/org/example/projektarendehantering/application/service/DocumentService.java`:
- Around line 81-85: The current byte-array-only check using TIKA.detect(byte[])
in DocumentService.java rejects real .docx files because Tika without parsers
returns application/zip; change the detection to use the filename hint by
calling TIKA.detect(fileBytes, file.getOriginalFilename() or filename) instead
of TIKA.detect(fileBytes) (i.e., replace the TIKA.detect(byte[]) call), and add
a unit test that uploads a real .docx fixture to exercise the docx path;
alternatively, if you prefer stronger detection, include
org.apache.tika:tika-parsers-standard-package and switch to a
DefaultDetector/TikaInputStream-based detection flow, but for a minimal fix use
TIKA.detect(byte[], String) and update the BadRequestException check
accordingly.

---

Nitpick comments:
In
`@src/main/java/org/example/projektarendehantering/application/service/DocumentService.java`:
- Around line 70-79: In DocumentService.java, make the filename-to-extension
normalization use Locale.ROOT (e.g., call toLowerCase(Locale.ROOT) on the
substring derived from originalFilename) and collapse the two identical
BadRequestException branches into one check: look up expectedMime from
EXTENSION_TO_MIME by the normalized extension and if expectedMime is null or not
equalIgnoreCase to contentType, throw the single BadRequestException with the
existing message; update references to originalFilename, EXTENSION_TO_MIME, and
contentType accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82871ae0-e6b9-4410-aa9b-3869e37fdb7a

📥 Commits

Reviewing files that changed from the base of the PR and between 0db0b5f and edf2bcc.

📒 Files selected for processing (4)
  • pom.xml
  • src/main/java/org/example/projektarendehantering/application/service/DocumentService.java
  • src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java
  • src/test/java/org/example/projektarendehantering/application/service/DocumentServiceTest.java
✅ Files skipped from review due to trivial changes (1)
  • pom.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java
  • src/test/java/org/example/projektarendehantering/application/service/DocumentServiceTest.java

@mattknatt
mattknatt merged commit c3f080b into main Apr 23, 2026
2 checks passed
@mattknatt
mattknatt deleted the feat/MIME-validation branch April 23, 2026 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Meta validation on files

1 participant