mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-05-24 10:52:00 +00:00
Fix cert-sign API NullPointerException when pageNumber is omitted for invisible signatures (#3463)
# Description of Changes Please provide a summary of the changes, including: - **What was changed** - Updated `SignPDFWithCertRequest` to use `Boolean` for `showSignature` and `showLogo`, and made `pageNumber` nullable. - In `CertSignController`: - Added an `@InitBinder` to convert empty multipart fields to `null`. - Extended `@PostMapping` to consume both `multipart/form-data` and `application/x-www-form-urlencoded`. - Wrapped `pageNumber` calculation in a null-check (`pageNumber = request.getPageNumber() != null ? request.getPageNumber() - 1 : null`). - Changed signature-visualization and logo checks to `Boolean.TRUE.equals(...)` to avoid unboxing NPE. - Cleaned up imports and schema annotations in the request model. - **Why the change was made** - Prevent a 500 Internal Server Error caused by calling `.intValue()` on a null `pageNumber` when `showSignature=false` (invisible signatures). - Ensure that omitting `pageNumber` doesn’t break clients using the “try it out” swagger UI or `curl`-based requests. - **Any challenges encountered** - Configuring Spring’s data binder to treat empty file inputs as `null` required a custom `PropertyEditorSupport`. - Balancing backward compatibility with stricter type handling (switching from primitive `boolean` to boxed `Boolean`). Closes #3459 --- ## 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/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/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/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/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
parent
e2a5874a88
commit
2ac606608a
@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.beans.PropertyEditorSupport;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
@ -53,7 +54,10 @@ import org.bouncycastle.operator.OperatorCreationException;
|
|||||||
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
||||||
import org.bouncycastle.pkcs.PKCSException;
|
import org.bouncycastle.pkcs.PKCSException;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -82,6 +86,18 @@ public class CertSignController {
|
|||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InitBinder
|
||||||
|
public void initBinder(WebDataBinder binder) {
|
||||||
|
binder.registerCustomEditor(
|
||||||
|
MultipartFile.class,
|
||||||
|
new PropertyEditorSupport() {
|
||||||
|
@Override
|
||||||
|
public void setAsText(String text) throws IllegalArgumentException {
|
||||||
|
setValue(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
private static void sign(
|
private static void sign(
|
||||||
@ -103,8 +119,7 @@ public class CertSignController {
|
|||||||
signature.setLocation(location);
|
signature.setLocation(location);
|
||||||
signature.setReason(reason);
|
signature.setReason(reason);
|
||||||
signature.setSignDate(Calendar.getInstance());
|
signature.setSignDate(Calendar.getInstance());
|
||||||
|
if (Boolean.TRUE.equals(showSignature)) {
|
||||||
if (showSignature) {
|
|
||||||
SignatureOptions signatureOptions = new SignatureOptions();
|
SignatureOptions signatureOptions = new SignatureOptions();
|
||||||
signatureOptions.setVisualSignature(
|
signatureOptions.setVisualSignature(
|
||||||
instance.createVisibleSignature(doc, signature, pageNumber, showLogo));
|
instance.createVisibleSignature(doc, signature, pageNumber, showLogo));
|
||||||
@ -121,7 +136,12 @@ public class CertSignController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
@PostMapping(
|
||||||
|
consumes = {
|
||||||
|
MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||||
|
MediaType.APPLICATION_FORM_URLENCODED_VALUE
|
||||||
|
},
|
||||||
|
value = "/cert-sign")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Sign PDF with a Digital Certificate",
|
summary = "Sign PDF with a Digital Certificate",
|
||||||
description =
|
description =
|
||||||
@ -137,12 +157,13 @@ public class CertSignController {
|
|||||||
MultipartFile p12File = request.getP12File();
|
MultipartFile p12File = request.getP12File();
|
||||||
MultipartFile jksfile = request.getJksFile();
|
MultipartFile jksfile = request.getJksFile();
|
||||||
String password = request.getPassword();
|
String password = request.getPassword();
|
||||||
Boolean showSignature = request.isShowSignature();
|
Boolean showSignature = request.getShowSignature();
|
||||||
String reason = request.getReason();
|
String reason = request.getReason();
|
||||||
String location = request.getLocation();
|
String location = request.getLocation();
|
||||||
String name = request.getName();
|
String name = request.getName();
|
||||||
Integer pageNumber = request.getPageNumber() - 1;
|
// Convert 1-indexed page number (user input) to 0-indexed page number (API requirement)
|
||||||
Boolean showLogo = request.isShowLogo();
|
Integer pageNumber = request.getPageNumber() != null ? (request.getPageNumber() - 1) : null;
|
||||||
|
Boolean showLogo = request.getShowLogo();
|
||||||
|
|
||||||
if (certType == null) {
|
if (certType == null) {
|
||||||
throw new IllegalArgumentException("Cert type must be provided");
|
throw new IllegalArgumentException("Cert type must be provided");
|
||||||
@ -279,7 +300,7 @@ public class CertSignController {
|
|||||||
widget.setAppearance(appearance);
|
widget.setAppearance(appearance);
|
||||||
|
|
||||||
try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream)) {
|
try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream)) {
|
||||||
if (showLogo) {
|
if (Boolean.TRUE.equals(showLogo)) {
|
||||||
cs.saveGraphicsState();
|
cs.saveGraphicsState();
|
||||||
PDExtendedGraphicsState extState = new PDExtendedGraphicsState();
|
PDExtendedGraphicsState extState = new PDExtendedGraphicsState();
|
||||||
extState.setBlendMode(BlendMode.MULTIPLY);
|
extState.setBlendMode(BlendMode.MULTIPLY);
|
||||||
|
@ -20,7 +20,8 @@ public class SignPDFWithCertRequest extends PDFFile {
|
|||||||
|
|
||||||
@Schema(
|
@Schema(
|
||||||
description =
|
description =
|
||||||
"The private key for the digital certificate (required for PEM type certificates)")
|
"The private key for the digital certificate (required for PEM type"
|
||||||
|
+ " certificates)")
|
||||||
private MultipartFile privateKeyFile;
|
private MultipartFile privateKeyFile;
|
||||||
|
|
||||||
@Schema(description = "The digital certificate (required for PEM type certificates)")
|
@Schema(description = "The digital certificate (required for PEM type certificates)")
|
||||||
@ -32,11 +33,11 @@ public class SignPDFWithCertRequest extends PDFFile {
|
|||||||
@Schema(description = "The JKS keystore file (Java Key Store)")
|
@Schema(description = "The JKS keystore file (Java Key Store)")
|
||||||
private MultipartFile jksFile;
|
private MultipartFile jksFile;
|
||||||
|
|
||||||
@Schema(description = "The password for the keystore or the private key")
|
@Schema(description = "The password for the keystore or the private key", format = "password")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Schema(description = "Whether to visually show the signature in the PDF file")
|
@Schema(description = "Whether to visually show the signature in the PDF file")
|
||||||
private boolean showSignature;
|
private Boolean showSignature;
|
||||||
|
|
||||||
@Schema(description = "The reason for signing the PDF")
|
@Schema(description = "The reason for signing the PDF")
|
||||||
private String reason;
|
private String reason;
|
||||||
@ -49,9 +50,10 @@ public class SignPDFWithCertRequest extends PDFFile {
|
|||||||
|
|
||||||
@Schema(
|
@Schema(
|
||||||
description =
|
description =
|
||||||
"The page number where the signature should be visible. This is required if showSignature is set to true")
|
"The page number where the signature should be visible. This is required if"
|
||||||
|
+ " showSignature is set to true")
|
||||||
private Integer pageNumber;
|
private Integer pageNumber;
|
||||||
|
|
||||||
@Schema(description = "Whether to visually show a signature logo along with the signature")
|
@Schema(description = "Whether to visually show a signature logo along with the signature")
|
||||||
private boolean showLogo;
|
private Boolean showLogo;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user