From 5275866f092c415e0ab44df801bcbc653680ca5a Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 3 Feb 2023 20:26:35 +0000 Subject: [PATCH] PDF security features (#26) - Support for adding and removing passwords - Support for watermarks - Dedicated page remover - Support for PDF permissions - Removed endpoint /home and replaced with / - Code cleanups - Fixed page titles --- README.md | 3 + build.gradle | 2 +- .../SPDF/controller/CompressController.java | 71 +++----- .../SPDF/controller/MergeController.java | 77 ++++++++ .../controller/OverlayImageController.java | 7 +- .../SPDF/controller/PdfController.java | 53 +----- .../RearrangePagesPDFController.java | 100 ++++++----- .../SPDF/controller/RotationController.java | 16 +- .../ConvertImgPDFController.java} | 54 +++--- .../security/PasswordController.java | 92 ++++++++++ .../security/WatermarkController.java | 83 +++++++++ .../software/SPDF/utils/PdfUtils.java | 44 +++++ src/main/resources/static/general.css | 17 +- src/main/resources/templates/add-image.html | 19 +- src/main/resources/templates/common.html | 95 ---------- .../resources/templates/compress-pdf.html | 35 ++-- src/main/resources/templates/convert-pdf.html | 61 ------- .../templates/convert/img-to-pdf.html | 38 ++++ .../templates/convert/pdf-to-img.html | 43 +++++ .../resources/templates/delete-pages.html | 42 +++++ .../resources/templates/fragments/card.html | 9 + .../resources/templates/fragments/common.html | 98 ++++++++++ .../templates/{ => fragments}/footer.html | 4 +- .../resources/templates/fragments/navbar.html | 87 +++++++++ src/main/resources/templates/home.html | 127 +++++-------- src/main/resources/templates/merge-pdfs.html | 167 ++++++++++-------- src/main/resources/templates/navbar.html | 49 ----- .../resources/templates/pdf-organizer.html | 18 +- src/main/resources/templates/rotate-pdf.html | 20 ++- .../templates/security/add-password.html | 103 +++++++++++ .../templates/security/add-watermark.html | 56 ++++++ .../security/change-permissions.html | 93 ++++++++++ .../templates/security/remove-password.html | 38 ++++ src/main/resources/templates/split-pdfs.html | 20 ++- 34 files changed, 1245 insertions(+), 596 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/controller/MergeController.java rename src/main/java/stirling/software/SPDF/controller/{ConvertPDFController.java => converters/ConvertImgPDFController.java} (58%) create mode 100644 src/main/java/stirling/software/SPDF/controller/security/PasswordController.java create mode 100644 src/main/java/stirling/software/SPDF/controller/security/WatermarkController.java delete mode 100644 src/main/resources/templates/common.html delete mode 100644 src/main/resources/templates/convert-pdf.html create mode 100644 src/main/resources/templates/convert/img-to-pdf.html create mode 100644 src/main/resources/templates/convert/pdf-to-img.html create mode 100644 src/main/resources/templates/delete-pages.html create mode 100644 src/main/resources/templates/fragments/card.html create mode 100644 src/main/resources/templates/fragments/common.html rename src/main/resources/templates/{ => fragments}/footer.html (84%) create mode 100644 src/main/resources/templates/fragments/navbar.html delete mode 100644 src/main/resources/templates/navbar.html create mode 100644 src/main/resources/templates/security/add-password.html create mode 100644 src/main/resources/templates/security/add-watermark.html create mode 100644 src/main/resources/templates/security/change-permissions.html create mode 100644 src/main/resources/templates/security/remove-password.html diff --git a/README.md b/README.md index dac4bcaed69..39c9c79b8ec 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ I will support and fix/add things to this if there is a demand [Discord](https:/ - Add images to PDFs at specified locations. - Rotating PDFs in 90 degree increments. - Compressing PDFs to decrease their filesize. +- Add and remove passwords +- Set PDF Permissions +- Add watermark(s) - Dark mode support. ## Technologies used diff --git a/build.gradle b/build.gradle index 1ad95df15f0..ba0b9acf9c5 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'stirling.software' -version = '0.2.3' +version = '0.3.0' sourceCompatibility = '17' repositories { diff --git a/src/main/java/stirling/software/SPDF/controller/CompressController.java b/src/main/java/stirling/software/SPDF/controller/CompressController.java index bffd19f075a..6b06a5ef129 100644 --- a/src/main/java/stirling/software/SPDF/controller/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/CompressController.java @@ -25,6 +25,7 @@ import com.spire.pdf.graphics.PdfBitmap; import stirling.software.SPDF.utils.PdfUtils; + //import com.spire.pdf.*; @Controller public class CompressController { @@ -40,48 +41,34 @@ public String compressPdfForm(Model model) { @PostMapping("/compress-pdf") public ResponseEntity compressPDF(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("imageCompressionLevel") String imageCompressionLevel) throws IOException { - - - //Load a sample PDF document - PdfDocument document = new PdfDocument(); - document.loadFromBytes(pdfFile.getBytes()); - - //Compress PDF - document.getFileInfo().setIncrementalUpdate(false); - document.setCompressionLevel(PdfCompressionLevel.Best); - - //compress PDF Images - for (int i = 0; i < document.getPages().getCount(); i++) { - - PdfPageBase page = document.getPages().get(i); - PdfImageInfo[] images = page.getImagesInfo(); - if (images != null && images.length > 0) - for (int j = 0; j < images.length; j++) { - PdfImageInfo image = images[j]; - PdfBitmap bp = new PdfBitmap(image.getImage()); - //bp.setPngDirectToJpeg(true); - bp.setQuality(Integer.valueOf(imageCompressionLevel)); - - page.replaceImage(j, bp); - - } - } - - // Save the rearranged PDF to a ByteArrayOutputStream - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - document.saveToStream(outputStream); - - // Close the original document - document.close(); - - // Prepare the response headers - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_PDF); - headers.setContentDispositionFormData("attachment", "compressed.pdf"); - headers.setContentLength(outputStream.size()); - - // Return the response with the PDF data and headers - return new ResponseEntity<>(outputStream.toByteArray(), headers, HttpStatus.OK); + + // Load a sample PDF document + PdfDocument document = new PdfDocument(); + document.loadFromBytes(pdfFile.getBytes()); + + // Compress PDF + document.getFileInfo().setIncrementalUpdate(false); + document.setCompressionLevel(PdfCompressionLevel.Best); + + // compress PDF Images + for (int i = 0; i < document.getPages().getCount(); i++) { + + PdfPageBase page = document.getPages().get(i); + PdfImageInfo[] images = page.getImagesInfo(); + if (images != null && images.length > 0) + for (int j = 0; j < images.length; j++) { + PdfImageInfo image = images[j]; + PdfBitmap bp = new PdfBitmap(image.getImage()); + // bp.setPngDirectToJpeg(true); + bp.setQuality(Integer.valueOf(imageCompressionLevel)); + + page.replaceImage(j, bp); + + } + } + + return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_compressed.pdf"); + } } diff --git a/src/main/java/stirling/software/SPDF/controller/MergeController.java b/src/main/java/stirling/software/SPDF/controller/MergeController.java new file mode 100644 index 00000000000..1c0a2ec5424 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/MergeController.java @@ -0,0 +1,77 @@ +package stirling.software.SPDF.controller; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageTree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.InputStreamResource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +@Controller +public class MergeController { + + private static final Logger logger = LoggerFactory.getLogger(MergeController.class); + + @GetMapping("/merge-pdfs") + public String hello(Model model) { + model.addAttribute("currentPage", "merge-pdfs"); + return "merge-pdfs"; + } + + @PostMapping("/merge-pdfs") + public ResponseEntity mergePdfs(@RequestParam("fileInput") MultipartFile[] files) + throws IOException { + // Read the input PDF files into PDDocument objects + List documents = new ArrayList<>(); + + // Loop through the files array and read each file into a PDDocument + for (MultipartFile file : files) { + documents.add(PDDocument.load(file.getInputStream())); + } + + PDDocument mergedDoc = mergeDocuments(documents); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + mergedDoc.save(byteArrayOutputStream); + mergedDoc.close(); + + // Create an InputStreamResource from the merged PDF + InputStreamResource resource = new InputStreamResource( + new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); + + // Return the merged PDF as a response + return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(resource); + } + + private PDDocument mergeDocuments(List documents) throws IOException { + // Create a new empty document + PDDocument mergedDoc = new PDDocument(); + + // Iterate over the list of documents and add their pages to the merged document + for (PDDocument doc : documents) { + // Get all pages from the current document + PDPageTree pages = doc.getPages(); + // Iterate over the pages and add them to the merged document + for (PDPage page : pages) { + mergedDoc.addPage(page); + } + } + + // Return the merged document + return mergedDoc; + } + +} \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/OverlayImageController.java b/src/main/java/stirling/software/SPDF/controller/OverlayImageController.java index f2a28469f82..281b9112fb6 100644 --- a/src/main/java/stirling/software/SPDF/controller/OverlayImageController.java +++ b/src/main/java/stirling/software/SPDF/controller/OverlayImageController.java @@ -36,11 +36,8 @@ public ResponseEntity overlayImage(@RequestParam("fileInput") MultipartF byte[] pdfBytes = pdfFile.getBytes(); byte[] imageBytes = imageFile.getBytes(); byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_PDF); - headers.setContentDispositionFormData("attachment", "overlayed.pdf"); - headers.setCacheControl("must-revalidate, post-check=0, pre-check=0"); - return new ResponseEntity<>(result, headers, HttpStatus.OK); + + return PdfUtils.bytesToWebResponse(result, pdfFile.getName() + "_overlayed.pdf"); } catch (IOException e) { logger.error("Failed to add image to PDF", e); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); diff --git a/src/main/java/stirling/software/SPDF/controller/PdfController.java b/src/main/java/stirling/software/SPDF/controller/PdfController.java index e66fb9a50ef..cff7df24a6a 100644 --- a/src/main/java/stirling/software/SPDF/controller/PdfController.java +++ b/src/main/java/stirling/software/SPDF/controller/PdfController.java @@ -26,63 +26,18 @@ public class PdfController { private static final Logger logger = LoggerFactory.getLogger(PdfController.class); - @GetMapping("/") + @GetMapping("/home") public String root(Model model) { - return "redirect:/home"; + return "redirect:/"; } - @GetMapping("/merge-pdfs") - public String hello(Model model) { - model.addAttribute("currentPage", "merge-pdfs"); - return "merge-pdfs"; - } - @GetMapping("/home") + @GetMapping("/") public String home(Model model) { model.addAttribute("currentPage", "home"); return "home"; } - @PostMapping("/merge-pdfs") - public ResponseEntity mergePdfs(@RequestParam("fileInput") MultipartFile[] files) - throws IOException { - // Read the input PDF files into PDDocument objects - List documents = new ArrayList<>(); - - // Loop through the files array and read each file into a PDDocument - for (MultipartFile file : files) { - documents.add(PDDocument.load(file.getInputStream())); - } - - PDDocument mergedDoc = mergeDocuments(documents); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - mergedDoc.save(byteArrayOutputStream); - mergedDoc.close(); - - // Create an InputStreamResource from the merged PDF - InputStreamResource resource = new InputStreamResource( - new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); - - // Return the merged PDF as a response - return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(resource); - } - - private PDDocument mergeDocuments(List documents) throws IOException { - // Create a new empty document - PDDocument mergedDoc = new PDDocument(); - - // Iterate over the list of documents and add their pages to the merged document - for (PDDocument doc : documents) { - // Get all pages from the current document - PDPageTree pages = doc.getPages(); - // Iterate over the pages and add them to the merged document - for (PDPage page : pages) { - mergedDoc.addPage(page); - } - } - - // Return the merged document - return mergedDoc; - } + } \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/RearrangePagesPDFController.java b/src/main/java/stirling/software/SPDF/controller/RearrangePagesPDFController.java index c07ee4e179c..7750f6865af 100644 --- a/src/main/java/stirling/software/SPDF/controller/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/RearrangePagesPDFController.java @@ -20,6 +20,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; +import stirling.software.SPDF.utils.PdfUtils; + @Controller public class RearrangePagesPDFController { @@ -31,6 +33,60 @@ public String pageOrganizer(Model model) { return "pdf-organizer"; } + @GetMapping("/delete-pages") + public String pageDeleter(Model model) { + model.addAttribute("currentPage", "delete-pages"); + return "delete-pages"; + } + + @PostMapping("/delete-pages") + public ResponseEntity deletePages(@RequestParam("fileInput") MultipartFile pdfFile, + @RequestParam("pagesToDelete") String pagesToDelete) throws IOException { + + PDDocument document = PDDocument.load(pdfFile.getBytes()); + + // Split the page order string into an array of page numbers or range of numbers + String[] pageOrderArr = pagesToDelete.split(","); + + List pagesToRemove = pageOrderToString(pageOrderArr, document.getNumberOfPages()); + + for (int i = pagesToRemove.size() - 1; i >= 0; i--) { + int pageIndex = pagesToRemove.get(i); + document.removePage(pageIndex); + } + + return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_removed_pages.pdf"); + + } + + private List pageOrderToString(String[] pageOrderArr, int totalPages) { + List newPageOrder = new ArrayList(); + // loop through the page order array + for (String element : pageOrderArr) { + // check if the element contains a range of pages + if (element.contains("-")) { + // split the range into start and end page + String[] range = element.split("-"); + int start = Integer.parseInt(range[0]); + int end = Integer.parseInt(range[1]); + // check if the end page is greater than total pages + if (end > totalPages) { + end = totalPages; + } + // loop through the range of pages + for (int j = start; j <= end; j++) { + // print the current index + newPageOrder.add(j - 1); + } + } else { + // if the element is a single page + newPageOrder.add(Integer.parseInt(element) - 1); + } + } + + return newPageOrder; + } + @PostMapping("/rearrange-pages") public ResponseEntity rearrangePages(@RequestParam("fileInput") MultipartFile pdfFile, @RequestParam("pageOrder") String pageOrder) { @@ -40,33 +96,11 @@ public ResponseEntity rearrangePages(@RequestParam("fileInput") Multipar // Split the page order string into an array of page numbers or range of numbers String[] pageOrderArr = pageOrder.split(","); - List newPageOrder = new ArrayList(); - //int[] newPageOrder = new int[pageOrderArr.length]; + // int[] newPageOrder = new int[pageOrderArr.length]; int totalPages = document.getNumberOfPages(); - // loop through the page order array - for (String element : pageOrderArr) { - // check if the element contains a range of pages - if (element.contains("-")) { - // split the range into start and end page - String[] range = element.split("-"); - int start = Integer.parseInt(range[0]); - int end = Integer.parseInt(range[1]); - // check if the end page is greater than total pages - if (end > totalPages) { - end = totalPages; - } - // loop through the range of pages - for (int j = start; j <= end; j++) { - // print the current index - newPageOrder.add( j - 1); - } - } else { - // if the element is a single page - newPageOrder.add( Integer.parseInt(element) - 1); - } - } - + List newPageOrder = pageOrderToString(pageOrderArr, totalPages); + // Create a new list to hold the pages in the new order List newPages = new ArrayList<>(); for (int i = 0; i < newPageOrder.size(); i++) { @@ -83,21 +117,7 @@ public ResponseEntity rearrangePages(@RequestParam("fileInput") Multipar document.addPage(page); } - // Save the rearranged PDF to a ByteArrayOutputStream - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - document.save(outputStream); - - // Close the original document - document.close(); - - // Prepare the response headers - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_PDF); - headers.setContentDispositionFormData("attachment", "rearranged.pdf"); - headers.setContentLength(outputStream.size()); - - // Return the response with the PDF data and headers - return new ResponseEntity<>(outputStream.toByteArray(), headers, HttpStatus.OK); + return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rearranged.pdf"); } catch (IOException e) { logger.error("Failed rearranging documents", e); diff --git a/src/main/java/stirling/software/SPDF/controller/RotationController.java b/src/main/java/stirling/software/SPDF/controller/RotationController.java index 233b2067c5a..42676f6dc5d 100644 --- a/src/main/java/stirling/software/SPDF/controller/RotationController.java +++ b/src/main/java/stirling/software/SPDF/controller/RotationController.java @@ -53,21 +53,7 @@ public ResponseEntity rotatePDF(@RequestParam("fileInput") MultipartFile page.setRotation(Integer.valueOf(angle)); } - // Save the rearranged PDF to a ByteArrayOutputStream - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - document.save(outputStream); - - // Close the document - document.close(); - - // Prepare the response headers - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_PDF); - headers.setContentDispositionFormData("attachment", "output.pdf"); - headers.setContentLength(outputStream.size()); - - // Return the response with the PDF data and headers - return new ResponseEntity<>(outputStream.toByteArray(), headers, HttpStatus.OK); + return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_rotated.pdf"); } diff --git a/src/main/java/stirling/software/SPDF/controller/ConvertPDFController.java b/src/main/java/stirling/software/SPDF/controller/converters/ConvertImgPDFController.java similarity index 58% rename from src/main/java/stirling/software/SPDF/controller/ConvertPDFController.java rename to src/main/java/stirling/software/SPDF/controller/converters/ConvertImgPDFController.java index c37c5e8a16f..5a943183cd5 100644 --- a/src/main/java/stirling/software/SPDF/controller/ConvertPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/converters/ConvertImgPDFController.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.controller; +package stirling.software.SPDF.controller.converters; import java.io.IOException; @@ -18,37 +18,37 @@ import stirling.software.SPDF.utils.PdfUtils; @Controller -public class ConvertPDFController { +public class ConvertImgPDFController { - private static final Logger logger = LoggerFactory.getLogger(ConvertPDFController.class); + private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class); - @GetMapping("/convert-pdf") + @GetMapping("/img-to-pdf") public String convertToPdfForm(Model model) { - model.addAttribute("currentPage", "convert-pdf"); - return "convert-pdf"; + model.addAttribute("currentPage", "img-to-pdf"); + return "convert/img-to-pdf"; } - @PostMapping("/convert-to-pdf") + @GetMapping("/pdf-to-img") + public String pdfToimgForm(Model model) { + model.addAttribute("currentPage", "pdf-to-img"); + return "convert/pdf-to-img"; + } + + @PostMapping("/img-to-pdf") public ResponseEntity convertToPdf(@RequestParam("fileInput") MultipartFile file) throws IOException { // Convert the file to PDF and get the resulting bytes byte[] bytes = PdfUtils.convertToPdf(file.getInputStream()); logger.info("File {} successfully converted to pdf", file.getOriginalFilename()); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_PDF); - String filename = "converted.pdf"; - headers.setContentDispositionFormData(filename, filename); - headers.setCacheControl("must-revalidate, post-check=0, pre-check=0"); - ResponseEntity response = new ResponseEntity<>(bytes, headers, HttpStatus.OK); - return response; + return PdfUtils.bytesToWebResponse(bytes, file.getName() + "_coverted.pdf"); } - - @PostMapping("/convert-from-pdf") + + @PostMapping("/pdf-to-img") public ResponseEntity convertToImage(@RequestParam("fileInput") MultipartFile file, @RequestParam("imageFormat") String imageFormat) throws IOException { byte[] pdfBytes = file.getBytes(); - //returns bytes for image - byte[] result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toLowerCase()); + // returns bytes for image + byte[] result = PdfUtils.convertFromPdf(pdfBytes, imageFormat.toLowerCase()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat))); headers.setCacheControl("must-revalidate, post-check=0, pre-check=0"); @@ -57,14 +57,14 @@ public ResponseEntity convertToImage(@RequestParam("fileInput") Multipar } private String getMediaType(String imageFormat) { - if(imageFormat.equalsIgnoreCase("PNG")) - return "image/png"; - else if(imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG")) - return "image/jpeg"; - else if(imageFormat.equalsIgnoreCase("GIF")) - return "image/gif"; - else - return "application/octet-stream"; + if (imageFormat.equalsIgnoreCase("PNG")) + return "image/png"; + else if (imageFormat.equalsIgnoreCase("JPEG") || imageFormat.equalsIgnoreCase("JPG")) + return "image/jpeg"; + else if (imageFormat.equalsIgnoreCase("GIF")) + return "image/gif"; + else + return "application/octet-stream"; } - + } diff --git a/src/main/java/stirling/software/SPDF/controller/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/security/PasswordController.java new file mode 100644 index 00000000000..1d7a79c51f8 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/security/PasswordController.java @@ -0,0 +1,92 @@ +package stirling.software.SPDF.controller.security; + +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.encryption.AccessPermission; +import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial; +import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import stirling.software.SPDF.utils.PdfUtils; + +@Controller +public class PasswordController { + + private static final Logger logger = LoggerFactory.getLogger(PasswordController.class); + + @GetMapping("/add-password") + public String addPasswordForm(Model model) { + model.addAttribute("currentPage", "add-password"); + return "security/add-password"; + } + + @GetMapping("/remove-password") + public String removePasswordForm(Model model) { + model.addAttribute("currentPage", "remove-password"); + return "security/remove-password"; + } + + @GetMapping("/change-permissions") + public String permissionsForm(Model model) { + model.addAttribute("currentPage", "change-permissions"); + return "security/change-permissions"; + } + + @PostMapping("/remove-password") + public ResponseEntity compressPDF(@RequestParam("fileInput") MultipartFile fileInput, + @RequestParam(name = "password") String password) throws IOException { + PDDocument document = PDDocument.load(fileInput.getBytes(), password); + document.setAllSecurityToBeRemoved(true); + return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_password_removed.pdf"); + } + + @PostMapping("/add-password") + public ResponseEntity compressPDF(@RequestParam("fileInput") MultipartFile fileInput, + @RequestParam(defaultValue = "", name = "password") String password, + @RequestParam(defaultValue = "128", name = "keyLength") int keyLength, + @RequestParam(defaultValue = "false", name = "canAssembleDocument") boolean canAssembleDocument, + @RequestParam(defaultValue = "false", name = "canExtractContent") boolean canExtractContent, + @RequestParam(defaultValue = "false", name = "canExtractForAccessibility") boolean canExtractForAccessibility, + @RequestParam(defaultValue = "false", name = "canFillInForm") boolean canFillInForm, + @RequestParam(defaultValue = "false", name = "canModify") boolean canModify, + @RequestParam(defaultValue = "false", name = "canModifyAnnotations") boolean canModifyAnnotations, + @RequestParam(defaultValue = "false", name = "canPrint") boolean canPrint, + @RequestParam(defaultValue = "false", name = "canPrintFaithful") boolean canPrintFaithful) + throws IOException { + + PDDocument document = PDDocument.load(fileInput.getBytes()); + AccessPermission ap = new AccessPermission(); + + ap.setCanAssembleDocument(!canAssembleDocument); + ap.setCanExtractContent(!canExtractContent); + ap.setCanExtractForAccessibility(!canExtractForAccessibility); + ap.setCanFillInForm(!canFillInForm); + ap.setCanModify(!canModify); + ap.setCanModifyAnnotations(!canModifyAnnotations); + ap.setCanPrint(!canPrint); + ap.setCanPrintFaithful(!canPrintFaithful); + StandardProtectionPolicy spp = new StandardProtectionPolicy(password, password, ap); + spp.setEncryptionKeyLength(keyLength); + + spp.setPermissions(ap); + + document.protect(spp); + + return PdfUtils.pdfDocToWebResponse(document, fileInput.getName() + "_passworded.pdf"); + } + +} diff --git a/src/main/java/stirling/software/SPDF/controller/security/WatermarkController.java b/src/main/java/stirling/software/SPDF/controller/security/WatermarkController.java new file mode 100644 index 00000000000..ba42a1d787f --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/security/WatermarkController.java @@ -0,0 +1,83 @@ +package stirling.software.SPDF.controller.security; + +import java.awt.Color; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; +import org.apache.pdfbox.util.Matrix; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import stirling.software.SPDF.utils.PdfUtils; + +@Controller +public class WatermarkController { + + @GetMapping("/add-watermark") + public String addWatermarkForm(Model model) { + model.addAttribute("currentPage", "add-watermark"); + return "security/add-watermark"; + } + + @PostMapping("/add-watermark") + public ResponseEntity addWatermark(@RequestParam("pdfFile") MultipartFile pdfFile, + @RequestParam("watermarkText") String watermarkText, + @RequestParam(defaultValue = "30", name = "fontSize") float fontSize, + @RequestParam(defaultValue = "0", name = "rotation") float rotation, + @RequestParam(defaultValue = "50", name = "widthSpacer") int widthSpacer, + @RequestParam(defaultValue = "50", name = "heightSpacer") int heightSpacer) throws IOException { + + // Load the input PDF + PDDocument document = PDDocument.load(pdfFile.getInputStream()); + + // Create a page in the document + for (PDPage page : document.getPages()) { + // Get the page's content stream + PDPageContentStream contentStream = new PDPageContentStream(document, page, + PDPageContentStream.AppendMode.APPEND, true); + + // Set font of watermark + PDFont font = PDType1Font.HELVETICA_BOLD; + contentStream.beginText(); + contentStream.setFont(font, fontSize); + contentStream.setNonStrokingColor(Color.LIGHT_GRAY); + + // Set size and location of watermark + float pageWidth = page.getMediaBox().getWidth(); + float pageHeight = page.getMediaBox().getHeight(); + float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000; + float watermarkHeight = heightSpacer + fontSize; + int watermarkRows = (int) (pageHeight / watermarkHeight + 1); + int watermarkCols = (int) (pageWidth / watermarkWidth + 1); + + // Add the watermark text + for (int i = 0; i < watermarkRows; i++) { + for (int j = 0; j < watermarkCols; j++) { + contentStream.setTextMatrix(Matrix.getRotateInstance((float) Math.toRadians(rotation), + j * watermarkWidth, i * watermarkHeight)); + contentStream.showTextWithPositioning(new Object[] { watermarkText }); + } + } + + contentStream.endText(); + + // Close the content stream + contentStream.close(); + } + return PdfUtils.pdfDocToWebResponse(document, pdfFile.getName() + "_watermarked.pdf"); + } +} diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 9fc24f281a4..43c235d62c1 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -22,6 +22,12 @@ import org.apache.pdfbox.rendering.PDFRenderer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; + +import com.spire.pdf.PdfDocument; public class PdfUtils { @@ -121,4 +127,42 @@ public static byte[] overlayImage(byte[] pdfBytes, byte[] imageBytes, float x, f throw e; } } + + public static ResponseEntity pdfDocToWebResponse(PdfDocument document, String docName) throws IOException { + + // Open Byte Array and save document to it + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.saveToStream(baos); + // Close the document + document.close(); + + return PdfUtils.boasToWebResponse(baos, docName); + } + + public static ResponseEntity pdfDocToWebResponse(PDDocument document, String docName) throws IOException { + + // Open Byte Array and save document to it + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + // Close the document + document.close(); + + return PdfUtils.boasToWebResponse(baos, docName); + } + + public static ResponseEntity boasToWebResponse(ByteArrayOutputStream baos, String docName) + throws IOException { + return PdfUtils.bytesToWebResponse(baos.toByteArray(), docName); + + } + + public static ResponseEntity bytesToWebResponse(byte[] bytes, String docName) throws IOException { + + // Return the PDF as a response + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_PDF); + headers.setContentLength(bytes.length); + headers.setContentDispositionFormData("attachment", docName); + return new ResponseEntity<>(bytes, headers, HttpStatus.OK); + } } diff --git a/src/main/resources/static/general.css b/src/main/resources/static/general.css index 20e97c598cb..a7c3d38f080 100644 --- a/src/main/resources/static/general.css +++ b/src/main/resources/static/general.css @@ -1,5 +1,14 @@ -#footer { - position: absolute; - bottom: 0; - width: 100%; +#page-container { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +#content-wrap { + flex: 1; } + +#footer { +bottom: 0; +width: 100%; +} \ No newline at end of file diff --git a/src/main/resources/templates/add-image.html b/src/main/resources/templates/add-image.html index f0c81be0a83..111775dd7c4 100644 --- a/src/main/resources/templates/add-image.html +++ b/src/main/resources/templates/add-image.html @@ -1,19 +1,20 @@ - - -S-PDF Add-Image - + + + -
+
+
+


-

Add image to PDF

+

Add image to PDF (Work in progress)

@@ -40,12 +41,14 @@

Add image to PDF

- +
-
+
+
+ \ No newline at end of file diff --git a/src/main/resources/templates/common.html b/src/main/resources/templates/common.html deleted file mode 100644 index cd79325307c..00000000000 --- a/src/main/resources/templates/common.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - -
-
- - - - - -
\ No newline at end of file diff --git a/src/main/resources/templates/compress-pdf.html b/src/main/resources/templates/compress-pdf.html index fdf47a26dc4..7631ba4e64d 100644 --- a/src/main/resources/templates/compress-pdf.html +++ b/src/main/resources/templates/compress-pdf.html @@ -1,43 +1,46 @@ - - -S-PDF Add-Image - - -
+ + + +
+
+


Compress PDF

- - - -
-

Warning: This process can take up to a minute depending on file-size

+ +

Warning: This process can take up to a minute depending on + file-size

- +
- +
-
+
+
+
\ No newline at end of file diff --git a/src/main/resources/templates/convert-pdf.html b/src/main/resources/templates/convert-pdf.html deleted file mode 100644 index caa89d055fc..00000000000 --- a/src/main/resources/templates/convert-pdf.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - -S-PDF ConvertToPDF - - - -
-
-
-
-
-
-

Image to PDF

- -
-
- -
-

- - -
- -
-
-
-

-
-
-
-

PDF to img

-
-
- -
-
- -
- -
- - -
-
-
-
- - \ No newline at end of file diff --git a/src/main/resources/templates/convert/img-to-pdf.html b/src/main/resources/templates/convert/img-to-pdf.html new file mode 100644 index 00000000000..48d8427ba79 --- /dev/null +++ b/src/main/resources/templates/convert/img-to-pdf.html @@ -0,0 +1,38 @@ + + + + + + +
+
+
+
+
+
+
+
+

Image to PDF

+ +
+
+ +
+
+
+ + +
+ +
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/convert/pdf-to-img.html b/src/main/resources/templates/convert/pdf-to-img.html new file mode 100644 index 00000000000..c37e0c8126f --- /dev/null +++ b/src/main/resources/templates/convert/pdf-to-img.html @@ -0,0 +1,43 @@ + + + + + + +
+
+
+ +
+
+
+
+
+

PDF to Image

+
+
+ +
+
+ +
+ +
+ + +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/delete-pages.html b/src/main/resources/templates/delete-pages.html new file mode 100644 index 00000000000..fb192b3c0ea --- /dev/null +++ b/src/main/resources/templates/delete-pages.html @@ -0,0 +1,42 @@ + + + + + + +
+
+
+
+
+
+
+
+

PDF Page remover

+ +
+
+ +
+
+ +
+ +
+ + +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/fragments/card.html b/src/main/resources/templates/fragments/card.html new file mode 100644 index 00000000000..c87bb3845da --- /dev/null +++ b/src/main/resources/templates/fragments/card.html @@ -0,0 +1,9 @@ +
+
+
+
+

+ Go +
+
+
\ No newline at end of file diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html new file mode 100644 index 00000000000..ed4c1505a01 --- /dev/null +++ b/src/main/resources/templates/fragments/common.html @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + +
+
+ + + + + +
\ No newline at end of file diff --git a/src/main/resources/templates/footer.html b/src/main/resources/templates/fragments/footer.html similarity index 84% rename from src/main/resources/templates/footer.html rename to src/main/resources/templates/fragments/footer.html index b4c75af1637..641035f5705 100644 --- a/src/main/resources/templates/footer.html +++ b/src/main/resources/templates/fragments/footer.html @@ -2,8 +2,8 @@