fix(api): prevent MultipartFile binding errors in StampController (#4331)

# Description of Changes

- **What was changed**
- Added a Spring `@InitBinder` in `StampController` that registers a
`PropertyEditorSupport` for `MultipartFile` to safely handle text inputs
by setting the value to `null`.
- Replaced multiple `switch` statements with modern Java **switch
expressions**:
    - Margin selection (`customMargin`) now uses a concise expression.
- Font selection (`alphabet` → font resource path) rewritten as an
expression.
- Position calculations (`calculatePositionX` / `calculatePositionY`)
now return via switch expressions with `yield`.
- Minor readability and maintainability improvements without changing
public API or behavior (besides the binding fix).

- **Why the change was made**
- The `@InitBinder` prevents conversion/binding issues when Spring
attempts to map string form fields to `MultipartFile`, which could cause
exceptions or unexpected behavior in multipart/form-data requests.
- Using switch expressions reduces boilerplate, clarifies intent, and
makes the control flow safer and more maintainable.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
Ludy 2025-09-04 14:55:09 +02:00 committed by GitHub
parent 6f6f4a14dc
commit a4a57cef92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api.misc;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.beans.PropertyEditorSupport;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -25,6 +26,8 @@ import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -52,6 +55,24 @@ public class StampController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final TempFileManager tempFileManager;
/**
* Initialize data binder for multipart file uploads.
* This method registers a custom editor for MultipartFile to handle file uploads.
* It sets the MultipartFile to null if the uploaded file is empty.
* This is necessary to avoid binding errors when the file is not present.
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(
MultipartFile.class,
new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(null);
}
});
}
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
@Operation(
summary = "Add stamp to a PDF file",
@ -91,25 +112,14 @@ public class StampController {
float overrideY = request.getOverrideY(); // New field for Y override
String customColor = request.getCustomColor();
float marginFactor;
switch (request.getCustomMargin().toLowerCase()) {
case "small":
marginFactor = 0.02f;
break;
case "medium":
marginFactor = 0.035f;
break;
case "large":
marginFactor = 0.05f;
break;
case "x-large":
marginFactor = 0.075f;
break;
default:
marginFactor = 0.035f;
break;
}
float marginFactor =
switch (request.getCustomMargin().toLowerCase()) {
case "small" -> 0.02f;
case "medium" -> 0.035f;
case "large" -> 0.05f;
case "x-large" -> 0.075f;
default -> 0.035f;
};
// Load the input PDF
PDDocument document = pdfDocumentFactory.load(pdfFile);
@ -185,27 +195,16 @@ public class StampController {
throws IOException {
String resourceDir = "";
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
switch (alphabet) {
case "arabic":
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
break;
case "japanese":
resourceDir = "static/fonts/Meiryo.ttf";
break;
case "korean":
resourceDir = "static/fonts/malgun.ttf";
break;
case "chinese":
resourceDir = "static/fonts/SimSun.ttf";
break;
case "thai":
resourceDir = "static/fonts/NotoSansThai-Regular.ttf";
break;
case "roman":
default:
resourceDir = "static/fonts/NotoSans-Regular.ttf";
break;
}
resourceDir =
switch (alphabet) {
case "arabic" -> "static/fonts/NotoSansArabic-Regular.ttf";
case "japanese" -> "static/fonts/Meiryo.ttf";
case "korean" -> "static/fonts/malgun.ttf";
case "chinese" -> "static/fonts/SimSun.ttf";
case "thai" -> "static/fonts/NotoSansThai-Regular.ttf";
case "roman" -> "static/fonts/NotoSans-Regular.ttf";
default -> "static/fonts/NotoSans-Regular.ttf";
};
if (!"".equals(resourceDir)) {
ClassPathResource classPathResource = new ClassPathResource(resourceDir);
@ -327,30 +326,30 @@ public class StampController {
throws IOException {
float actualWidth =
(text != null) ? calculateTextWidth(text, font, fontSize) : contentWidth;
switch (position % 3) {
return switch (position % 3) {
case 1: // Left
return pageSize.getLowerLeftX() + margin;
yield pageSize.getLowerLeftX() + margin;
case 2: // Center
return (pageSize.getWidth() - actualWidth) / 2;
yield (pageSize.getWidth() - actualWidth) / 2;
case 0: // Right
return pageSize.getUpperRightX() - actualWidth - margin;
yield pageSize.getUpperRightX() - actualWidth - margin;
default:
return 0;
}
yield 0;
};
}
private float calculatePositionY(
PDRectangle pageSize, int position, float height, float margin) {
switch ((position - 1) / 3) {
return switch ((position - 1) / 3) {
case 0: // Top
return pageSize.getUpperRightY() - height - margin;
yield pageSize.getUpperRightY() - height - margin;
case 1: // Middle
return (pageSize.getHeight() - height) / 2;
yield (pageSize.getHeight() - height) / 2;
case 2: // Bottom
return pageSize.getLowerLeftY() + margin;
yield pageSize.getLowerLeftY() + margin;
default:
return 0;
}
yield 0;
};
}
private float calculateTextWidth(String text, PDFont font, float fontSize) throws IOException {