From ef12c2f892f655d06d50af99bdaf6ec6825ee242 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:39:21 +0000 Subject: [PATCH 1/5] Add ebook support --- .../software/SPDF/config/AppConfig.java | 37 +++++++ .../SPDF/config/EndpointConfiguration.java | 21 +++- .../SPDF/config/PostStartupProcesses.java | 95 ++++++++++++++++ .../security/UserAuthenticationFilter.java | 55 ++++++++-- .../ConvertBookToPDFController.java | 68 ++++++++++++ .../api/converters/ConvertHtmlToPDF.java | 10 +- .../api/converters/ConvertMarkdownToPdf.java | 10 +- .../ConvertPDFToBookController.java | 101 ++++++++++++++++++ .../api/converters/ConvertWebsiteToPDF.java | 12 ++- .../web/ConverterWebController.java | 17 +++ .../controller/web/HomeWebController.java | 6 +- .../SPDF/model/ApplicationProperties.java | 41 +++++++ .../api/converters/PdfToBookRequest.java | 19 ++++ .../software/SPDF/utils/FileToPdf.java | 49 ++++++++- .../software/SPDF/utils/ProcessExecutor.java | 54 ++++++---- src/main/resources/settings.yml.template | 4 +- src/main/resources/static/images/book.svg | 3 + .../static/js/multitool/PdfContainer.js | 29 ++--- .../templates/convert/book-to-pdf.html | 30 ++++++ .../templates/convert/pdf-to-book.html | 55 ++++++++++ .../resources/templates/fragments/navbar.html | 2 + src/main/resources/templates/home.html | 2 + 22 files changed, 668 insertions(+), 52 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/config/PostStartupProcesses.java create mode 100644 src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java create mode 100644 src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java create mode 100644 src/main/java/stirling/software/SPDF/model/api/converters/PdfToBookRequest.java create mode 100644 src/main/resources/static/images/book.svg create mode 100644 src/main/resources/templates/convert/book-to-pdf.html create mode 100644 src/main/resources/templates/convert/pdf-to-book.html diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index 0411715f..b740707a 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -1,5 +1,8 @@ package stirling.software.SPDF.config; +import java.nio.file.Files; +import java.nio.file.Paths; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -57,4 +60,38 @@ public class AppConfig { if (appName == null) appName = System.getenv("rateLimit"); return (appName != null) ? Boolean.valueOf(appName) : false; } + + @Bean(name = "RunningInDocker") + public boolean runningInDocker() { + return Files.exists(Paths.get("/.dockerenv")); + } + + @Bean(name = "bookFormatsInstalled") + public boolean bookFormatsInstalled() { + System.out.println("astirli " + applicationProperties.getSystem()); + System.out.println("astirli2 " + applicationProperties.getSystem().getCustomApplications()); + System.out.println( + "astirli3 " + + applicationProperties + .getSystem() + .getCustomApplications() + .isInstallBookFormats()); + return applicationProperties.getSystem().getCustomApplications().isInstallBookFormats(); + } + + @Bean(name = "htmlFormatsInstalled") + public boolean htmlFormatsInstalled() { + System.out.println("astirli4 " + applicationProperties.getSystem()); + System.out.println("astirli5 " + applicationProperties.getSystem().getCustomApplications()); + System.out.println( + "astirli6 " + + applicationProperties + .getSystem() + .getCustomApplications() + .isInstallAdvancedHtmlToPDF()); + return applicationProperties + .getSystem() + .getCustomApplications() + .isInstallAdvancedHtmlToPDF(); + } } diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index 593b70b4..a56949ae 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -9,11 +9,14 @@ import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Service; import stirling.software.SPDF.model.ApplicationProperties; @Service +@DependsOn({"bookFormatsInstalled"}) public class EndpointConfiguration { private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); private Map endpointStatuses = new ConcurrentHashMap<>(); @@ -21,9 +24,14 @@ public class EndpointConfiguration { private final ApplicationProperties applicationProperties; + private boolean bookFormatsInstalled; + @Autowired - public EndpointConfiguration(ApplicationProperties applicationProperties) { + public EndpointConfiguration( + ApplicationProperties applicationProperties, + @Qualifier("bookFormatsInstalled") boolean bookFormatsInstalled) { this.applicationProperties = applicationProperties; + this.bookFormatsInstalled = bookFormatsInstalled; init(); processEnvironmentConfigs(); } @@ -145,6 +153,12 @@ public class EndpointConfiguration { addEndpointToGroup("CLI", "ocr-pdf"); addEndpointToGroup("CLI", "html-to-pdf"); addEndpointToGroup("CLI", "url-to-pdf"); + addEndpointToGroup("CLI", "book-to-pdf"); + addEndpointToGroup("CLI", "pdf-to-book"); + + // Calibre + addEndpointToGroup("Calibre", "book-to-pdf"); + addEndpointToGroup("Calibre", "pdf-to-book"); // python addEndpointToGroup("Python", "extract-image-scans"); @@ -215,7 +229,10 @@ public class EndpointConfiguration { private void processEnvironmentConfigs() { List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); - + System.out.println("ASTIRLI7 bookFormatsInstalled=" + bookFormatsInstalled); + if (!bookFormatsInstalled) { + groupsToRemove.add("Calibre"); + } if (endpointsToRemove != null) { for (String endpoint : endpointsToRemove) { disableEndpoint(endpoint.trim()); diff --git a/src/main/java/stirling/software/SPDF/config/PostStartupProcesses.java b/src/main/java/stirling/software/SPDF/config/PostStartupProcesses.java new file mode 100644 index 00000000..09216a76 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/PostStartupProcesses.java @@ -0,0 +1,95 @@ +package stirling.software.SPDF.config; + +import java.io.IOException; +import java.util.*; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; + +@Component +public class PostStartupProcesses { + + @Autowired ApplicationProperties applicationProperties; + + @Autowired + @Qualifier("RunningInDocker") + private boolean runningInDocker; + + @Autowired + @Qualifier("bookFormatsInstalled") + private boolean bookFormatsInstalled; + + @Autowired + @Qualifier("htmlFormatsInstalled") + private boolean htmlFormatsInstalled; + + @PostConstruct + public void runInstallCommandBasedOnEnvironment() throws IOException, InterruptedException { + List> commands = new ArrayList<>(); + System.out.println("astirli bookFormatsInstalled=" + bookFormatsInstalled); + System.out.println("astirli htmlFormatsInstalled=" + htmlFormatsInstalled); + // Checking for DOCKER_INSTALL_BOOK_FORMATS environment variable + if (bookFormatsInstalled) { + List tmpList = new ArrayList<>(); + // Set up the timezone configuration commands + tmpList.addAll( + Arrays.asList( + "sh", + "-c", + "echo 'tzdata tzdata/Areas select Europe' | debconf-set-selections; " + + "echo 'tzdata tzdata/Zones/Europe select Berlin' | debconf-set-selections")); + commands.add(tmpList); + + // Install calibre with DEBIAN_FRONTEND set to noninteractive + tmpList = new ArrayList<>(); + tmpList.addAll( + Arrays.asList( + "sh", + "-c", + "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends calibre")); + commands.add(tmpList); + } + + // Checking for DOCKER_INSTALL_HTML_FORMATS environment variable + if (htmlFormatsInstalled) { + List tmpList = new ArrayList<>(); + // Add -y flag for automatic yes to prompts and --no-install-recommends to reduce size + tmpList.addAll( + Arrays.asList( + "apt-get", "install", "wkhtmltopdf", "-y", "--no-install-recommends")); + commands.add(tmpList); + } + + if (!commands.isEmpty()) { + // Run the command + if (runningInDocker) { + List tmpList = new ArrayList<>(); + tmpList.addAll(Arrays.asList("apt-get", "update")); + commands.add(0, tmpList); + + for (List list : commands) { + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.INSTALL_APP, true) + .runCommandWithOutputHandling(list); + System.out.println("astirli RC for app installs " + returnCode.getRc()); + } + } else { + System.out.println( + "astirli Not running inside Docker so skipping automated install process with command."); + } + + } else { + if (runningInDocker) { + System.out.println("astirli No custom apps to install."); + } else { + System.out.println("astirli No custom apps to install. and not docker"); + } + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java index 47423eb6..a6e8eff3 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java @@ -1,9 +1,12 @@ package stirling.software.SPDF.config.security; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; @@ -18,6 +21,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; import stirling.software.SPDF.model.ApiKeyAuthenticationToken; @Component @@ -31,14 +35,28 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { @Qualifier("loginEnabled") public boolean loginEnabledValue; + @Value("${redirect.port:}") // Default to empty if not set + private String redirectPort; + @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + // Custom response wrapper to modify the redirect location + HttpServletResponseWrapper responseWrapper = + new HttpServletResponseWrapper(response) { + @Override + public void sendRedirect(String location) throws IOException { + // Modify the location to include the correct port + String modifiedLocation = modifyLocation(location, request); + super.sendRedirect(modifiedLocation); + } + }; + if (!loginEnabledValue) { // If login is not enabled, just pass all requests without authentication - filterChain.doFilter(request, response); + filterChain.doFilter(request, responseWrapper); return; } String requestURI = request.getRequestURI(); @@ -53,8 +71,8 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { // provider for API keys. UserDetails userDetails = userService.loadUserByApiKey(apiKey); if (userDetails == null) { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter().write("Invalid API Key."); + responseWrapper.setStatus(HttpStatus.UNAUTHORIZED.value()); + responseWrapper.getWriter().write("Invalid API Key."); return; } authentication = @@ -63,8 +81,8 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { SecurityContextHolder.getContext().setAuthentication(authentication); } catch (AuthenticationException e) { // If API key authentication fails, deny the request - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter().write("Invalid API Key."); + responseWrapper.setStatus(HttpStatus.UNAUTHORIZED.value()); + responseWrapper.getWriter().write("Invalid API Key."); return; } } @@ -76,18 +94,37 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { String contextPath = request.getContextPath(); if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) { - response.sendRedirect(contextPath + "/login"); // redirect to the login page + responseWrapper.sendRedirect(contextPath + "/login"); // redirect to the login page return; } else { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter() + responseWrapper.setStatus(HttpStatus.UNAUTHORIZED.value()); + responseWrapper + .getWriter() .write( "Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected"); return; } } - filterChain.doFilter(request, response); + filterChain.doFilter(request, responseWrapper); + } + + private String modifyLocation(String location, HttpServletRequest request) { + if (!location.matches("https?://[^/]+:\\d+.*") + && redirectPort != null + && redirectPort.length() > 0) { + try { + int port = Integer.parseInt(redirectPort); // Parse the port + URL url = new URL(location); + String modifiedUrl = + new URL(url.getProtocol(), url.getHost(), port, url.getFile()).toString(); + return modifiedUrl; + } catch (MalformedURLException | NumberFormatException e) { + // Log error and return the original location if URL parsing fails + e.printStackTrace(); + } + } + return location; } @Override diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java new file mode 100644 index 00000000..453f8e6e --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java @@ -0,0 +1,68 @@ +package stirling.software.SPDF.controller.api.converters; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import stirling.software.SPDF.model.api.GeneralFile; +import stirling.software.SPDF.utils.FileToPdf; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@Tag(name = "Convert", description = "Convert APIs") +@RequestMapping("/api/v1/convert") +public class ConvertBookToPDFController { + + @Autowired + @Qualifier("bookFormatsInstalled") + private boolean bookFormatsInstalled; + + @PostMapping(consumes = "multipart/form-data", value = "/book/pdf") + @Operation( + summary = + "Convert a BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) to PDF", + description = + "(Requires bookFormatsInstalled flag and Calibre installed) This endpoint takes an BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) input and converts it to PDF format.") + public ResponseEntity HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception { + MultipartFile fileInput = request.getFileInput(); + + if (!bookFormatsInstalled) { + throw new IllegalArgumentException( + "bookFormatsInstalled flag is False, this functionality is not avaiable"); + } + + if (fileInput == null) { + throw new IllegalArgumentException("Please provide a file for conversion."); + } + + String originalFilename = fileInput.getOriginalFilename(); + + if (originalFilename != null) { + String originalFilenameLower = originalFilename.toLowerCase(); + if (!originalFilenameLower.endsWith(".epub") + && !originalFilenameLower.endsWith(".mobi") + && !originalFilenameLower.endsWith(".azw3") + && !originalFilenameLower.endsWith(".fb2") + && !originalFilenameLower.endsWith(".txt") + && !originalFilenameLower.endsWith(".docx")) { + throw new IllegalArgumentException( + "File must be in .epub, .mobi, .azw3, .fb2, .txt, or .docx format."); + } + } + byte[] pdfBytes = FileToPdf.convertBookTypeToPdf(fileInput.getBytes(), originalFilename); + + String outputFilename = + originalFilename.replaceFirst("[.][^.]+$", "") + + ".pdf"; // Remove file extension and append .pdf + + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java index bec09040..fdcd114a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java @@ -1,5 +1,7 @@ package stirling.software.SPDF.controller.api.converters; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -19,6 +21,10 @@ import stirling.software.SPDF.utils.WebResponseUtils; @RequestMapping("/api/v1/convert") public class ConvertHtmlToPDF { + @Autowired + @Qualifier("htmlFormatsInstalled") + private boolean htmlFormatsInstalled; + @PostMapping(consumes = "multipart/form-data", value = "/html/pdf") @Operation( summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF", @@ -37,7 +43,9 @@ public class ConvertHtmlToPDF { || (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) { throw new IllegalArgumentException("File must be either .html or .zip format."); } - byte[] pdfBytes = FileToPdf.convertHtmlToPdf(fileInput.getBytes(), originalFilename); + byte[] pdfBytes = + FileToPdf.convertHtmlToPdf( + fileInput.getBytes(), originalFilename, htmlFormatsInstalled); String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java index 8bdc5049..d0fd632d 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java @@ -3,6 +3,8 @@ package stirling.software.SPDF.controller.api.converters; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -22,6 +24,10 @@ import stirling.software.SPDF.utils.WebResponseUtils; @RequestMapping("/api/v1/convert") public class ConvertMarkdownToPdf { + @Autowired + @Qualifier("htmlFormatsInstalled") + private boolean htmlFormatsInstalled; + @PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf") @Operation( summary = "Convert a Markdown file to PDF", @@ -46,7 +52,9 @@ public class ConvertMarkdownToPdf { HtmlRenderer renderer = HtmlRenderer.builder().build(); String htmlContent = renderer.render(document); - byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html"); + byte[] pdfBytes = + FileToPdf.convertHtmlToPdf( + htmlContent.getBytes(), "converted.html", htmlFormatsInstalled); String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java new file mode 100644 index 00000000..1ee09d9e --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java @@ -0,0 +1,101 @@ +package stirling.software.SPDF.controller.api.converters; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import stirling.software.SPDF.model.api.converters.PdfToBookRequest; +import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@Tag(name = "Convert", description = "Convert APIs") +@RequestMapping("/api/v1/convert") +public class ConvertPDFToBookController { + + @Autowired + @Qualifier("bookFormatsInstalled") + private boolean bookFormatsInstalled; + + @PostMapping(consumes = "multipart/form-data", value = "/pdf/book") + @Operation( + summary = + "Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF", + description = + "(Requires bookFormatsInstalled flag and Calibre installed) This endpoint Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF") + public ResponseEntity HtmlToPdf(@ModelAttribute PdfToBookRequest request) + throws Exception { + MultipartFile fileInput = request.getFileInput(); + + if (!bookFormatsInstalled) { + throw new IllegalArgumentException( + "bookFormatsInstalled flag is False, this functionality is not avaiable"); + } + + if (fileInput == null) { + throw new IllegalArgumentException("Please provide a file for conversion."); + } + + // Validate the output format + String outputFormat = request.getOutputFormat().toLowerCase(); + List allowedFormats = + Arrays.asList( + "epub", "mobi", "azw3", "docx", "rtf", "txt", "html", "lit", "fb2", "pdb", + "lrf"); + if (!allowedFormats.contains(outputFormat)) { + throw new IllegalArgumentException("Invalid output format: " + outputFormat); + } + + byte[] outputFileBytes; + List command = new ArrayList<>(); + Path tempOutputFile = + Files.createTempFile( + "output_", + "." + outputFormat); // Use the output format for the file extension + Path tempInputFile = null; + + try { + // Create temp input file from the provided PDF + tempInputFile = Files.createTempFile("input_", ".pdf"); // Assuming input is always PDF + Files.write(tempInputFile, fileInput.getBytes()); + + command.add("ebook-convert"); + command.add(tempInputFile.toString()); + command.add(tempOutputFile.toString()); + + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE) + .runCommandWithOutputHandling(command); + + outputFileBytes = Files.readAllBytes(tempOutputFile); + } finally { + // Clean up temporary files + if (tempInputFile != null) { + Files.deleteIfExists(tempInputFile); + } + Files.deleteIfExists(tempOutputFile); + } + + String outputFilename = + fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + + "." + + outputFormat; // Remove file extension and append .pdf + + return WebResponseUtils.bytesToWebResponse(outputFileBytes, outputFilename); + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java index 815018e8..a6cd439b 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java @@ -6,6 +6,8 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -26,6 +28,10 @@ import stirling.software.SPDF.utils.WebResponseUtils; @RequestMapping("/api/v1/convert") public class ConvertWebsiteToPDF { + @Autowired + @Qualifier("htmlFormatsInstalled") + private boolean htmlFormatsInstalled; + @PostMapping(consumes = "multipart/form-data", value = "/url/pdf") @Operation( summary = "Convert a URL to a PDF", @@ -47,7 +53,11 @@ public class ConvertWebsiteToPDF { // Prepare the OCRmyPDF command List command = new ArrayList<>(); - command.add("weasyprint"); + if (!htmlFormatsInstalled) { + command.add("weasyprint"); + } else { + command.add("wkhtmltopdf"); + } command.add(URL); command.add(tempOutputFile.toString()); diff --git a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java index 16e42dec..55ebcb91 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java @@ -1,5 +1,6 @@ package stirling.software.SPDF.controller.web; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -12,6 +13,22 @@ import io.swagger.v3.oas.annotations.tags.Tag; @Tag(name = "Convert", description = "Convert APIs") public class ConverterWebController { + @ConditionalOnExpression("#{bookFormatsInstalled}") + @GetMapping("/book-to-pdf") + @Hidden + public String convertBookToPdfForm(Model model) { + model.addAttribute("currentPage", "book-to-pdf"); + return "convert/book-to-pdf"; + } + + @ConditionalOnExpression("#{bookFormatsInstalled}") + @GetMapping("/pdf-to-book") + @Hidden + public String convertPdfToBookForm(Model model) { + model.addAttribute("currentPage", "pdf-to-book"); + return "convert/pdf-to-book"; + } + @GetMapping("/img-to-pdf") @Hidden public String convertImgToPdfForm(Model model) { diff --git a/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java b/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java index 2426d523..94e83342 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/HomeWebController.java @@ -1,7 +1,8 @@ package stirling.software.SPDF.controller.web; import java.io.IOException; -import java.nio.file.Files; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; @@ -38,7 +39,8 @@ public class HomeWebController { model.addAttribute("currentPage", "licenses"); Resource resource = new ClassPathResource("static/3rdPartyLicenses.json"); try { - String json = new String(Files.readAllBytes(resource.getFile().toPath())); + InputStream is = resource.getInputStream(); + String json = new String(is.readAllBytes(), StandardCharsets.UTF_8); ObjectMapper mapper = new ObjectMapper(); Map> data = mapper.readValue(json, new TypeReference>>() {}); diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index a41d641c..3258d8b1 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -210,6 +210,7 @@ public class ApplicationProperties { private String rootURIPath; private String customStaticFilePath; private Integer maxFileSize; + private CustomApplications customApplications; private Boolean enableAlphaFunctionality; @@ -261,6 +262,14 @@ public class ApplicationProperties { this.maxFileSize = maxFileSize; } + public CustomApplications getCustomApplications() { + return customApplications != null ? customApplications : new CustomApplications(); + } + + public void setCustomApplications(CustomApplications customApplications) { + this.customApplications = customApplications; + } + @Override public String toString() { return "System [defaultLocale=" @@ -273,10 +282,42 @@ public class ApplicationProperties { + customStaticFilePath + ", maxFileSize=" + maxFileSize + + ", customApplications=" + + customApplications + ", enableAlphaFunctionality=" + enableAlphaFunctionality + "]"; } + + public static class CustomApplications { + private boolean installBookFormats; + private boolean installAdvancedHtmlToPDF; + + public boolean isInstallBookFormats() { + return installBookFormats; + } + + public void setInstallBookFormats(boolean installBookFormats) { + this.installBookFormats = installBookFormats; + } + + public boolean isInstallAdvancedHtmlToPDF() { + return installAdvancedHtmlToPDF; + } + + public void setInstallAdvancedHtmlToPDF(boolean installAdvancedHtmlToPDF) { + this.installAdvancedHtmlToPDF = installAdvancedHtmlToPDF; + } + + @Override + public String toString() { + return "CustomApplications [installBookFormats=" + + installBookFormats + + ", installAdvancedHtmlToPDF=" + + installAdvancedHtmlToPDF + + "]"; + } + } } public static class Ui { diff --git a/src/main/java/stirling/software/SPDF/model/api/converters/PdfToBookRequest.java b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToBookRequest.java new file mode 100644 index 00000000..b3454afb --- /dev/null +++ b/src/main/java/stirling/software/SPDF/model/api/converters/PdfToBookRequest.java @@ -0,0 +1,19 @@ +package stirling.software.SPDF.model.api.converters; + +import io.swagger.v3.oas.annotations.media.Schema; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import stirling.software.SPDF.model.api.PDFFile; + +@Data +@EqualsAndHashCode(callSuper = true) +public class PdfToBookRequest extends PDFFile { + + @Schema( + description = "The output Ebook format", + allowableValues = { + "epub", "mobi", "azw3", "docx", "rtf", "txt", "html", "lit", "fb2", "pdb", "lrf" + }) + private String outputFormat; +} diff --git a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java index 5e6825dd..ca06c5c2 100644 --- a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java +++ b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java @@ -14,7 +14,9 @@ import java.util.zip.ZipInputStream; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; public class FileToPdf { - public static byte[] convertHtmlToPdf(byte[] fileBytes, String fileName) + + public static byte[] convertHtmlToPdf( + byte[] fileBytes, String fileName, boolean htmlFormatsInstalled) throws IOException, InterruptedException { Path tempOutputFile = Files.createTempFile("output_", ".pdf"); @@ -29,11 +31,20 @@ public class FileToPdf { } List command = new ArrayList<>(); - command.add("weasyprint"); + if (!htmlFormatsInstalled) { + command.add("weasyprint"); + } else { + command.add("wkhtmltopdf"); + } command.add(tempInputFile.toString()); command.add(tempOutputFile.toString()); ProcessExecutorResult returnCode; if (fileName.endsWith(".zip")) { + + if (htmlFormatsInstalled) { + command.add("--allow"); + command.add(tempOutputFile.getParent().toString()); + } returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) .runCommandWithOutputHandling( @@ -97,4 +108,38 @@ public class FileToPdf { return htmlFiles.get(0); } } + + public static byte[] convertBookTypeToPdf(byte[] bytes, String originalFilename) + throws IOException, InterruptedException { + if (originalFilename == null || originalFilename.lastIndexOf('.') == -1) { + throw new IllegalArgumentException("Invalid original filename."); + } + + String fileExtension = originalFilename.substring(originalFilename.lastIndexOf('.')); + List command = new ArrayList<>(); + Path tempOutputFile = Files.createTempFile("output_", ".pdf"); + Path tempInputFile = null; + + try { + // Create temp file with appropriate extension + tempInputFile = Files.createTempFile("input_", fileExtension); + Files.write(tempInputFile, bytes); + + command.add("ebook-convert"); + command.add(tempInputFile.toString()); + command.add(tempOutputFile.toString()); + + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE) + .runCommandWithOutputHandling(command); + + return Files.readAllBytes(tempOutputFile); + } finally { + // Clean up temporary files + if (tempInputFile != null) { + Files.deleteIfExists(tempInputFile); + } + Files.deleteIfExists(tempOutputFile); + } + } } diff --git a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java index 385f3b80..18bc925a 100644 --- a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java +++ b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java @@ -18,12 +18,18 @@ public class ProcessExecutor { OCR_MY_PDF, PYTHON_OPENCV, GHOSTSCRIPT, - WEASYPRINT + WEASYPRINT, + INSTALL_APP, + CALIBRE } private static final Map instances = new ConcurrentHashMap<>(); public static ProcessExecutor getInstance(Processes processType) { + return getInstance(processType, false); + } + + public static ProcessExecutor getInstance(Processes processType, boolean liveUpdates) { return instances.computeIfAbsent( processType, key -> { @@ -34,15 +40,19 @@ public class ProcessExecutor { case PYTHON_OPENCV -> 8; case GHOSTSCRIPT -> 16; case WEASYPRINT -> 16; + case INSTALL_APP -> 1; + case CALIBRE -> 1; }; - return new ProcessExecutor(semaphoreLimit); + return new ProcessExecutor(semaphoreLimit, liveUpdates); }); } private final Semaphore semaphore; + private final boolean liveUpdates; - private ProcessExecutor(int semaphoreLimit) { + private ProcessExecutor(int semaphoreLimit, boolean liveUpdates) { this.semaphore = new Semaphore(semaphoreLimit); + this.liveUpdates = liveUpdates; } public ProcessExecutorResult runCommandWithOutputHandling(List command) @@ -81,6 +91,7 @@ public class ProcessExecutor { String line; while ((line = errorReader.readLine()) != null) { errorLines.add(line); + if (liveUpdates) System.out.println(line); } } catch (IOException e) { e.printStackTrace(); @@ -98,6 +109,7 @@ public class ProcessExecutor { String line; while ((line = outputReader.readLine()) != null) { outputLines.add(line); + if (liveUpdates) System.out.println(line); } } catch (IOException e) { e.printStackTrace(); @@ -114,23 +126,27 @@ public class ProcessExecutor { errorReaderThread.join(); outputReaderThread.join(); - if (outputLines.size() > 0) { - String outputMessage = String.join("\n", outputLines); - messages += outputMessage; - System.out.println("Command output:\n" + outputMessage); - } - - if (errorLines.size() > 0) { - String errorMessage = String.join("\n", errorLines); - messages += errorMessage; - System.out.println("Command error output:\n" + errorMessage); - if (exitCode != 0) { - throw new IOException( - "Command process failed with exit code " - + exitCode - + ". Error message: " - + errorMessage); + if (!liveUpdates) { + if (outputLines.size() > 0) { + String outputMessage = String.join("\n", outputLines); + messages += outputMessage; + System.out.println("Command output:\n" + outputMessage); } + + if (errorLines.size() > 0) { + String errorMessage = String.join("\n", errorLines); + messages += errorMessage; + System.out.println("Command error output:\n" + errorMessage); + if (exitCode != 0) { + throw new IOException( + "Command process failed with exit code " + + exitCode + + ". Error message: " + + errorMessage); + } + } + } else if (exitCode != 0) { + throw new IOException("Command process failed with exit code " + exitCode); } } finally { semaphore.release(); diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index 52d5e4de..1245c9e0 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -9,10 +9,12 @@ security: loginResetTimeMinutes : 120 # lock account for 2 hours after x attempts system: + defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc) googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes) - +# customExternalPort: 8000 used for when port mappings do not work correctly + #ui: # appName: exampleAppName # Application's visible name # homeDescription: I am a description # Short description or tagline shown on homepage. diff --git a/src/main/resources/static/images/book.svg b/src/main/resources/static/images/book.svg new file mode 100644 index 00000000..302acf09 --- /dev/null +++ b/src/main/resources/static/images/book.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js index 4c7c8497..4a53c961 100644 --- a/src/main/resources/static/js/multitool/PdfContainer.js +++ b/src/main/resources/static/js/multitool/PdfContainer.js @@ -209,20 +209,21 @@ class PdfContainer { async exportPdf() { const pdfDoc = await PDFLib.PDFDocument.create(); - for (var i=0; i + + + + + +
+
+
+

+
+
+
+

+
+
+
+ + +
+

+

+
+
+
+
+
+
+ + diff --git a/src/main/resources/templates/convert/pdf-to-book.html b/src/main/resources/templates/convert/pdf-to-book.html new file mode 100644 index 00000000..a136c5cf --- /dev/null +++ b/src/main/resources/templates/convert/pdf-to-book.html @@ -0,0 +1,55 @@ + + + + + + + +
+
+
+

+
+
+
+

+
+
+ +
+ + +
+
+ + +
+

+

+
+
+
+
+
+
+ + diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index 8e5ce732..b090b08c 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -77,6 +77,7 @@
+
@@ -86,6 +87,7 @@
+
diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 946cdd9c..52f945eb 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -99,6 +99,8 @@
+
+
From e717d83f753305996cfc685207a14da070b90f79 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:33:07 +0000 Subject: [PATCH 2/5] fixes and timeouts --- .../software/SPDF/utils/FileToPdf.java | 6 +- .../software/SPDF/utils/ProcessExecutor.java | 55 +++++++++++++++---- src/main/resources/settings.yml.template | 4 +- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java index ca06c5c2..ebdbf4fa 100644 --- a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java +++ b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java @@ -35,15 +35,17 @@ public class FileToPdf { command.add("weasyprint"); } else { command.add("wkhtmltopdf"); + command.add("--enable-local-file-access"); } + command.add(tempInputFile.toString()); command.add(tempOutputFile.toString()); ProcessExecutorResult returnCode; if (fileName.endsWith(".zip")) { if (htmlFormatsInstalled) { - command.add("--allow"); - command.add(tempOutputFile.getParent().toString()); + // command.add(1, "--allow"); + // command.add(2, tempInputFile.getParent().toString()); } returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) diff --git a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java index 18bc925a..23311bde 100644 --- a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java +++ b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java @@ -4,15 +4,22 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.io.InterruptedIOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ProcessExecutor { + private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class); + public enum Processes { LIBRE_OFFICE, OCR_MY_PDF, @@ -26,7 +33,7 @@ public class ProcessExecutor { private static final Map instances = new ConcurrentHashMap<>(); public static ProcessExecutor getInstance(Processes processType) { - return getInstance(processType, false); + return getInstance(processType, true); } public static ProcessExecutor getInstance(Processes processType, boolean liveUpdates) { @@ -43,16 +50,29 @@ public class ProcessExecutor { case INSTALL_APP -> 1; case CALIBRE -> 1; }; - return new ProcessExecutor(semaphoreLimit, liveUpdates); + + long timeoutMinutes = + switch (key) { + case LIBRE_OFFICE -> 30; + case OCR_MY_PDF -> 30; + case PYTHON_OPENCV -> 30; + case GHOSTSCRIPT -> 5; + case WEASYPRINT -> 30; + case INSTALL_APP -> 60; + case CALIBRE -> 30; + }; + return new ProcessExecutor(semaphoreLimit, liveUpdates, timeoutMinutes); }); } private final Semaphore semaphore; private final boolean liveUpdates; + private long timeoutDuration; - private ProcessExecutor(int semaphoreLimit, boolean liveUpdates) { + private ProcessExecutor(int semaphoreLimit, boolean liveUpdates, long timeout) { this.semaphore = new Semaphore(semaphoreLimit); this.liveUpdates = liveUpdates; + this.timeoutDuration = timeout; } public ProcessExecutorResult runCommandWithOutputHandling(List command) @@ -62,12 +82,12 @@ public class ProcessExecutor { public ProcessExecutorResult runCommandWithOutputHandling( List command, File workingDirectory) throws IOException, InterruptedException { - int exitCode = 1; String messages = ""; + int exitCode = 1; semaphore.acquire(); try { - System.out.print("Running command: " + String.join(" ", command)); + logger.info("Running command: " + String.join(" ", command)); ProcessBuilder processBuilder = new ProcessBuilder(command); // Use the working directory if it's set @@ -91,8 +111,11 @@ public class ProcessExecutor { String line; while ((line = errorReader.readLine()) != null) { errorLines.add(line); - if (liveUpdates) System.out.println(line); + if (liveUpdates) logger.info(line); } + } catch (InterruptedIOException e) { + logger.warn( + "Error reader thread was interrupted due to timeout."); } catch (IOException e) { e.printStackTrace(); } @@ -109,8 +132,11 @@ public class ProcessExecutor { String line; while ((line = outputReader.readLine()) != null) { outputLines.add(line); - if (liveUpdates) System.out.println(line); + if (liveUpdates) logger.info(line); } + } catch (InterruptedIOException e) { + logger.warn( + "Error reader thread was interrupted due to timeout."); } catch (IOException e) { e.printStackTrace(); } @@ -120,8 +146,17 @@ public class ProcessExecutor { outputReaderThread.start(); // Wait for the conversion process to complete - exitCode = process.waitFor(); + boolean finished = process.waitFor(timeoutDuration, TimeUnit.MINUTES); + if (!finished) { + // Terminate the process + process.destroy(); + // Interrupt the reader threads + errorReaderThread.interrupt(); + outputReaderThread.interrupt(); + throw new IOException("Process timeout exceeded."); + } + exitCode = process.exitValue(); // Wait for the reader threads to finish errorReaderThread.join(); outputReaderThread.join(); @@ -130,13 +165,13 @@ public class ProcessExecutor { if (outputLines.size() > 0) { String outputMessage = String.join("\n", outputLines); messages += outputMessage; - System.out.println("Command output:\n" + outputMessage); + logger.info("Command output:\n" + outputMessage); } if (errorLines.size() > 0) { String errorMessage = String.join("\n", errorLines); messages += errorMessage; - System.out.println("Command error output:\n" + errorMessage); + logger.warn("Command error output:\n" + errorMessage); if (exitCode != 0) { throw new IOException( "Command process failed with exit code " diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index 1245c9e0..00c5998e 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -13,7 +13,9 @@ system: defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc) googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes) -# customExternalPort: 8000 used for when port mappings do not work correctly + customApplications: + installBookFormats: false # Installs Calibre for book format conversion (For non docker it must be manually downloaded but will need to be true to show in UI) + installAdvancedHtmlToPDF: false # DO NOT USE EXTERNALLY, NOT SAFE! Install wkHtmlToPDF (For non docker it must be manually downloaded but will need to be true to show in UI) #ui: # appName: exampleAppName # Application's visible name From 139c793b5e70f550bfd11c2e2b47fc623ad9aeec Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:33:22 +0000 Subject: [PATCH 3/5] 0.19.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f506ef01..40dda97f 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ plugins { import com.github.jk1.license.render.* group = 'stirling.software' -version = '0.19.0' +version = '0.19.1' sourceCompatibility = '17' repositories { From 32da14acbf85dd2f1f33cd67f3707ec820ffc392 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:37:55 +0000 Subject: [PATCH 4/5] log removal --- .../software/SPDF/config/AppConfig.java | 16 -------------- .../SPDF/config/EndpointConfiguration.java | 1 - .../SPDF/config/PostStartupProcesses.java | 21 +++++++++++-------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index b740707a..cf05fd1b 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -68,27 +68,11 @@ public class AppConfig { @Bean(name = "bookFormatsInstalled") public boolean bookFormatsInstalled() { - System.out.println("astirli " + applicationProperties.getSystem()); - System.out.println("astirli2 " + applicationProperties.getSystem().getCustomApplications()); - System.out.println( - "astirli3 " - + applicationProperties - .getSystem() - .getCustomApplications() - .isInstallBookFormats()); return applicationProperties.getSystem().getCustomApplications().isInstallBookFormats(); } @Bean(name = "htmlFormatsInstalled") public boolean htmlFormatsInstalled() { - System.out.println("astirli4 " + applicationProperties.getSystem()); - System.out.println("astirli5 " + applicationProperties.getSystem().getCustomApplications()); - System.out.println( - "astirli6 " - + applicationProperties - .getSystem() - .getCustomApplications() - .isInstallAdvancedHtmlToPDF()); return applicationProperties .getSystem() .getCustomApplications() diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index a56949ae..f1e328f9 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -229,7 +229,6 @@ public class EndpointConfiguration { private void processEnvironmentConfigs() { List endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); List groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); - System.out.println("ASTIRLI7 bookFormatsInstalled=" + bookFormatsInstalled); if (!bookFormatsInstalled) { groupsToRemove.add("Calibre"); } diff --git a/src/main/java/stirling/software/SPDF/config/PostStartupProcesses.java b/src/main/java/stirling/software/SPDF/config/PostStartupProcesses.java index 09216a76..862e5f9e 100644 --- a/src/main/java/stirling/software/SPDF/config/PostStartupProcesses.java +++ b/src/main/java/stirling/software/SPDF/config/PostStartupProcesses.java @@ -1,8 +1,12 @@ package stirling.software.SPDF.config; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -29,11 +33,11 @@ public class PostStartupProcesses { @Qualifier("htmlFormatsInstalled") private boolean htmlFormatsInstalled; + private static final Logger logger = LoggerFactory.getLogger(PostStartupProcesses.class); + @PostConstruct public void runInstallCommandBasedOnEnvironment() throws IOException, InterruptedException { List> commands = new ArrayList<>(); - System.out.println("astirli bookFormatsInstalled=" + bookFormatsInstalled); - System.out.println("astirli htmlFormatsInstalled=" + htmlFormatsInstalled); // Checking for DOCKER_INSTALL_BOOK_FORMATS environment variable if (bookFormatsInstalled) { List tmpList = new ArrayList<>(); @@ -77,18 +81,17 @@ public class PostStartupProcesses { ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.INSTALL_APP, true) .runCommandWithOutputHandling(list); - System.out.println("astirli RC for app installs " + returnCode.getRc()); + logger.info("RC for app installs {}", returnCode.getRc()); } } else { - System.out.println( - "astirli Not running inside Docker so skipping automated install process with command."); + + logger.info( + "Not running inside Docker so skipping automated install process with command."); } } else { if (runningInDocker) { - System.out.println("astirli No custom apps to install."); - } else { - System.out.println("astirli No custom apps to install. and not docker"); + logger.info("No custom apps to install."); } } } From 873a4ecb7e8f6b9a33709c7945ab0c3043338b4a Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:39:26 +0000 Subject: [PATCH 5/5] revert --- .../security/UserAuthenticationFilter.java | 57 ++++--------------- 1 file changed, 10 insertions(+), 47 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java index a6e8eff3..61b209de 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java @@ -1,12 +1,9 @@ package stirling.software.SPDF.config.security; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; @@ -21,7 +18,6 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpServletResponseWrapper; import stirling.software.SPDF.model.ApiKeyAuthenticationToken; @Component @@ -35,28 +31,14 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { @Qualifier("loginEnabled") public boolean loginEnabledValue; - @Value("${redirect.port:}") // Default to empty if not set - private String redirectPort; - @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - // Custom response wrapper to modify the redirect location - HttpServletResponseWrapper responseWrapper = - new HttpServletResponseWrapper(response) { - @Override - public void sendRedirect(String location) throws IOException { - // Modify the location to include the correct port - String modifiedLocation = modifyLocation(location, request); - super.sendRedirect(modifiedLocation); - } - }; - if (!loginEnabledValue) { // If login is not enabled, just pass all requests without authentication - filterChain.doFilter(request, responseWrapper); + filterChain.doFilter(request, response); return; } String requestURI = request.getRequestURI(); @@ -71,8 +53,8 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { // provider for API keys. UserDetails userDetails = userService.loadUserByApiKey(apiKey); if (userDetails == null) { - responseWrapper.setStatus(HttpStatus.UNAUTHORIZED.value()); - responseWrapper.getWriter().write("Invalid API Key."); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write("Invalid API Key."); return; } authentication = @@ -81,8 +63,8 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { SecurityContextHolder.getContext().setAuthentication(authentication); } catch (AuthenticationException e) { // If API key authentication fails, deny the request - responseWrapper.setStatus(HttpStatus.UNAUTHORIZED.value()); - responseWrapper.getWriter().write("Invalid API Key."); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write("Invalid API Key."); return; } } @@ -94,37 +76,18 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { String contextPath = request.getContextPath(); if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) { - responseWrapper.sendRedirect(contextPath + "/login"); // redirect to the login page + response.sendRedirect(contextPath + "/login"); // redirect to the login page return; } else { - responseWrapper.setStatus(HttpStatus.UNAUTHORIZED.value()); - responseWrapper - .getWriter() + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter() .write( "Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected"); return; } } - filterChain.doFilter(request, responseWrapper); - } - - private String modifyLocation(String location, HttpServletRequest request) { - if (!location.matches("https?://[^/]+:\\d+.*") - && redirectPort != null - && redirectPort.length() > 0) { - try { - int port = Integer.parseInt(redirectPort); // Parse the port - URL url = new URL(location); - String modifiedUrl = - new URL(url.getProtocol(), url.getHost(), port, url.getFile()).toString(); - return modifiedUrl; - } catch (MalformedURLException | NumberFormatException e) { - // Log error and return the original location if URL parsing fails - e.printStackTrace(); - } - } - return location; + filterChain.doFilter(request, response); } @Override @@ -152,4 +115,4 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { return false; } -} +} \ No newline at end of file