mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02: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; | ||||
| 
 | ||||
| import java.awt.*; | ||||
| import java.beans.PropertyEditorSupport; | ||||
| import java.io.*; | ||||
| import java.nio.file.Files; | ||||
| import java.security.*; | ||||
| @ -53,7 +54,10 @@ import org.bouncycastle.operator.OperatorCreationException; | ||||
| import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; | ||||
| import org.bouncycastle.pkcs.PKCSException; | ||||
| import org.springframework.core.io.ClassPathResource; | ||||
| import org.springframework.http.MediaType; | ||||
| 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; | ||||
| @ -82,6 +86,18 @@ public class CertSignController { | ||||
|         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 static void sign( | ||||
| @ -103,8 +119,7 @@ public class CertSignController { | ||||
|             signature.setLocation(location); | ||||
|             signature.setReason(reason); | ||||
|             signature.setSignDate(Calendar.getInstance()); | ||||
| 
 | ||||
|             if (showSignature) { | ||||
|             if (Boolean.TRUE.equals(showSignature)) { | ||||
|                 SignatureOptions signatureOptions = new SignatureOptions(); | ||||
|                 signatureOptions.setVisualSignature( | ||||
|                         instance.createVisibleSignature(doc, signature, pageNumber, showLogo)); | ||||
| @ -121,13 +136,18 @@ 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( | ||||
|             summary = "Sign PDF with a Digital Certificate", | ||||
|             description = | ||||
|                     "This endpoint accepts a PDF file, a digital certificate and related" | ||||
|                             + " information to sign the PDF. It then returns the digitally signed PDF" | ||||
|                             + " file. Input:PDF Output:PDF Type:SISO") | ||||
|                         + " information to sign the PDF. It then returns the digitally signed PDF" | ||||
|                         + " file. Input:PDF Output:PDF Type:SISO") | ||||
|     public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) | ||||
|             throws Exception { | ||||
|         MultipartFile pdf = request.getFileInput(); | ||||
| @ -137,12 +157,13 @@ public class CertSignController { | ||||
|         MultipartFile p12File = request.getP12File(); | ||||
|         MultipartFile jksfile = request.getJksFile(); | ||||
|         String password = request.getPassword(); | ||||
|         Boolean showSignature = request.isShowSignature(); | ||||
|         Boolean showSignature = request.getShowSignature(); | ||||
|         String reason = request.getReason(); | ||||
|         String location = request.getLocation(); | ||||
|         String name = request.getName(); | ||||
|         Integer pageNumber = request.getPageNumber() - 1; | ||||
|         Boolean showLogo = request.isShowLogo(); | ||||
|         // Convert 1-indexed page number (user input) to 0-indexed page number (API requirement) | ||||
|         Integer pageNumber = request.getPageNumber() != null ? (request.getPageNumber() - 1) : null; | ||||
|         Boolean showLogo = request.getShowLogo(); | ||||
| 
 | ||||
|         if (certType == null) { | ||||
|             throw new IllegalArgumentException("Cert type must be provided"); | ||||
| @ -279,7 +300,7 @@ public class CertSignController { | ||||
|                 widget.setAppearance(appearance); | ||||
| 
 | ||||
|                 try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream)) { | ||||
|                     if (showLogo) { | ||||
|                     if (Boolean.TRUE.equals(showLogo)) { | ||||
|                         cs.saveGraphicsState(); | ||||
|                         PDExtendedGraphicsState extState = new PDExtendedGraphicsState(); | ||||
|                         extState.setBlendMode(BlendMode.MULTIPLY); | ||||
|  | ||||
| @ -20,7 +20,8 @@ public class SignPDFWithCertRequest extends PDFFile { | ||||
| 
 | ||||
|     @Schema( | ||||
|             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; | ||||
| 
 | ||||
|     @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)") | ||||
|     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; | ||||
| 
 | ||||
|     @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") | ||||
|     private String reason; | ||||
| @ -49,9 +50,10 @@ public class SignPDFWithCertRequest extends PDFFile { | ||||
| 
 | ||||
|     @Schema( | ||||
|             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; | ||||
| 
 | ||||
|     @Schema(description = "Whether to visually show a signature logo along with the signature") | ||||
|     private boolean showLogo; | ||||
|     private Boolean showLogo; | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user