Make ./gradlew executable; accept .java files improvements after running ./gradlew build

This commit is contained in:
Andrey Voronkov 2024-01-03 03:21:11 +03:00
parent de9e9a0f84
commit 8a57165547
29 changed files with 3006 additions and 3007 deletions

0
gradlew vendored Normal file → Executable file
View file

View file

@ -1,81 +1,81 @@
package stirling.software.SPDF; package stirling.software.SPDF;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Collections; import java.util.Collections;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.config.ConfigInitializer; import stirling.software.SPDF.config.ConfigInitializer;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
@SpringBootApplication @SpringBootApplication
@EnableScheduling @EnableScheduling
public class SPdfApplication { public class SPdfApplication {
@Autowired private Environment env; @Autowired private Environment env;
@PostConstruct @PostConstruct
public void init() { public void init() {
// Check if the BROWSER_OPEN environment variable is set to true // Check if the BROWSER_OPEN environment variable is set to true
String browserOpenEnv = env.getProperty("BROWSER_OPEN"); String browserOpenEnv = env.getProperty("BROWSER_OPEN");
boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true"); boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true");
if (browserOpen) { if (browserOpen) {
try { try {
String url = "http://localhost:" + getPort(); String url = "http://localhost:" + getPort();
String os = System.getProperty("os.name").toLowerCase(); String os = System.getProperty("os.name").toLowerCase();
Runtime rt = Runtime.getRuntime(); Runtime rt = Runtime.getRuntime();
if (os.contains("win")) { if (os.contains("win")) {
// For Windows // For Windows
rt.exec("rundll32 url.dll,FileProtocolHandler " + url); rt.exec("rundll32 url.dll,FileProtocolHandler " + url);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication app = new SpringApplication(SPdfApplication.class); SpringApplication app = new SpringApplication(SPdfApplication.class);
app.addInitializers(new ConfigInitializer()); app.addInitializers(new ConfigInitializer());
if (Files.exists(Paths.get("configs/settings.yml"))) { if (Files.exists(Paths.get("configs/settings.yml"))) {
app.setDefaultProperties( app.setDefaultProperties(
Collections.singletonMap( Collections.singletonMap(
"spring.config.additional-location", "file:configs/settings.yml")); "spring.config.additional-location", "file:configs/settings.yml"));
} else { } else {
System.out.println( System.out.println(
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); "External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
} }
app.run(args); app.run(args);
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
GeneralUtils.createDir("customFiles/static/"); GeneralUtils.createDir("customFiles/static/");
GeneralUtils.createDir("customFiles/templates/"); GeneralUtils.createDir("customFiles/templates/");
System.out.println("Stirling-PDF Started."); System.out.println("Stirling-PDF Started.");
String url = "http://localhost:" + getPort(); String url = "http://localhost:" + getPort();
System.out.println("Navigate to " + url); System.out.println("Navigate to " + url);
} }
public static String getPort() { public static String getPort() {
String port = System.getProperty("local.server.port"); String port = System.getProperty("local.server.port");
if (port == null || port.isEmpty()) { if (port == null || port.isEmpty()) {
port = "8080"; port = "8080";
} }
return port; return port;
} }
} }

View file

@ -1,60 +1,60 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Configuration @Configuration
public class AppConfig { public class AppConfig {
@Autowired ApplicationProperties applicationProperties; @Autowired ApplicationProperties applicationProperties;
@Bean(name = "loginEnabled") @Bean(name = "loginEnabled")
public boolean loginEnabled() { public boolean loginEnabled() {
return applicationProperties.getSecurity().getEnableLogin(); return applicationProperties.getSecurity().getEnableLogin();
} }
@Bean(name = "appName") @Bean(name = "appName")
public String appName() { public String appName() {
String homeTitle = applicationProperties.getUi().getAppName(); String homeTitle = applicationProperties.getUi().getAppName();
return (homeTitle != null) ? homeTitle : "Stirling PDF"; return (homeTitle != null) ? homeTitle : "Stirling PDF";
} }
@Bean(name = "appVersion") @Bean(name = "appVersion")
public String appVersion() { public String appVersion() {
String version = getClass().getPackage().getImplementationVersion(); String version = getClass().getPackage().getImplementationVersion();
return (version != null) ? version : "0.0.0"; return (version != null) ? version : "0.0.0";
} }
@Bean(name = "homeText") @Bean(name = "homeText")
public String homeText() { public String homeText() {
return (applicationProperties.getUi().getHomeDescription() != null) return (applicationProperties.getUi().getHomeDescription() != null)
? applicationProperties.getUi().getHomeDescription() ? applicationProperties.getUi().getHomeDescription()
: "null"; : "null";
} }
@Bean(name = "navBarText") @Bean(name = "navBarText")
public String navBarText() { public String navBarText() {
String defaultNavBar = String defaultNavBar =
applicationProperties.getUi().getAppNameNavbar() != null applicationProperties.getUi().getAppNameNavbar() != null
? applicationProperties.getUi().getAppNameNavbar() ? applicationProperties.getUi().getAppNameNavbar()
: applicationProperties.getUi().getAppName(); : applicationProperties.getUi().getAppName();
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF"; return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
} }
@Bean(name = "enableAlphaFunctionality") @Bean(name = "enableAlphaFunctionality")
public boolean enableAlphaFunctionality() { public boolean enableAlphaFunctionality() {
return applicationProperties.getSystem().getEnableAlphaFunctionality() != null return applicationProperties.getSystem().getEnableAlphaFunctionality() != null
? applicationProperties.getSystem().getEnableAlphaFunctionality() ? applicationProperties.getSystem().getEnableAlphaFunctionality()
: false; : false;
} }
@Bean(name = "rateLimit") @Bean(name = "rateLimit")
public boolean rateLimit() { public boolean rateLimit() {
String appName = System.getProperty("rateLimit"); String appName = System.getProperty("rateLimit");
if (appName == null) appName = System.getenv("rateLimit"); if (appName == null) appName = System.getenv("rateLimit");
return (appName != null) ? Boolean.valueOf(appName) : false; return (appName != null) ? Boolean.valueOf(appName) : false;
} }
} }

View file

@ -1,64 +1,64 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.util.Locale; import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver; import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Configuration @Configuration
public class Beans implements WebMvcConfigurer { public class Beans implements WebMvcConfigurer {
@Autowired ApplicationProperties applicationProperties; @Autowired ApplicationProperties applicationProperties;
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor()); registry.addInterceptor(localeChangeInterceptor());
registry.addInterceptor(new CleanUrlInterceptor()); registry.addInterceptor(new CleanUrlInterceptor());
} }
@Bean @Bean
public LocaleChangeInterceptor localeChangeInterceptor() { public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang"); lci.setParamName("lang");
return lci; return lci;
} }
@Bean @Bean
public LocaleResolver localeResolver() { public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver(); SessionLocaleResolver slr = new SessionLocaleResolver();
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale(); String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();
Locale defaultLocale = Locale defaultLocale =
Locale.UK; // Fallback to UK locale if environment variable is not set Locale.UK; // Fallback to UK locale if environment variable is not set
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) { if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv); Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
String tempLanguageTag = tempLocale.toLanguageTag(); String tempLanguageTag = tempLocale.toLanguageTag();
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
defaultLocale = tempLocale; defaultLocale = tempLocale;
} else { } else {
tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-")); tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-"));
tempLanguageTag = tempLocale.toLanguageTag(); tempLanguageTag = tempLocale.toLanguageTag();
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
defaultLocale = tempLocale; defaultLocale = tempLocale;
} else { } else {
System.err.println( System.err.println(
"Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK."); "Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK.");
} }
} }
} }
slr.setDefaultLocale(defaultLocale); slr.setDefaultLocale(defaultLocale);
return slr; return slr;
} }
} }

View file

@ -1,74 +1,74 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
public class CleanUrlInterceptor implements HandlerInterceptor { public class CleanUrlInterceptor implements HandlerInterceptor {
private static final List<String> ALLOWED_PARAMS = private static final List<String> ALLOWED_PARAMS =
Arrays.asList( Arrays.asList(
"lang", "endpoint", "endpoints", "logout", "error", "file", "messageType"); "lang", "endpoint", "endpoints", "logout", "error", "file", "messageType");
@Override @Override
public boolean preHandle( public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { throws Exception {
String queryString = request.getQueryString(); String queryString = request.getQueryString();
if (queryString != null && !queryString.isEmpty()) { if (queryString != null && !queryString.isEmpty()) {
String requestURI = request.getRequestURI(); String requestURI = request.getRequestURI();
Map<String, String> parameters = new HashMap<>(); Map<String, String> parameters = new HashMap<>();
// Keep only the allowed parameters // Keep only the allowed parameters
String[] queryParameters = queryString.split("&"); String[] queryParameters = queryString.split("&");
for (String param : queryParameters) { for (String param : queryParameters) {
String[] keyValue = param.split("="); String[] keyValue = param.split("=");
if (keyValue.length != 2) { if (keyValue.length != 2) {
continue; continue;
} }
if (ALLOWED_PARAMS.contains(keyValue[0])) { if (ALLOWED_PARAMS.contains(keyValue[0])) {
parameters.put(keyValue[0], keyValue[1]); parameters.put(keyValue[0], keyValue[1]);
} }
} }
// If there are any parameters that are not allowed // If there are any parameters that are not allowed
if (parameters.size() != queryParameters.length) { if (parameters.size() != queryParameters.length) {
// Construct new query string // Construct new query string
StringBuilder newQueryString = new StringBuilder(); StringBuilder newQueryString = new StringBuilder();
for (Map.Entry<String, String> entry : parameters.entrySet()) { for (Map.Entry<String, String> entry : parameters.entrySet()) {
if (newQueryString.length() > 0) { if (newQueryString.length() > 0) {
newQueryString.append("&"); newQueryString.append("&");
} }
newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); newQueryString.append(entry.getKey()).append("=").append(entry.getValue());
} }
// Redirect to the URL with only allowed query parameters // Redirect to the URL with only allowed query parameters
String redirectUrl = requestURI + "?" + newQueryString; String redirectUrl = requestURI + "?" + newQueryString;
response.sendRedirect(redirectUrl); response.sendRedirect(redirectUrl);
return false; return false;
} }
} }
return true; return true;
} }
@Override @Override
public void postHandle( public void postHandle(
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
Object handler, Object handler,
ModelAndView modelAndView) {} ModelAndView modelAndView) {}
@Override @Override
public void afterCompletion( public void afterCompletion(
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
Object handler, Object handler,
Exception ex) {} Exception ex) {}
} }

View file

@ -1,231 +1,231 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Service @Service
public class EndpointConfiguration { public class EndpointConfiguration {
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>(); private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>(); private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
@Autowired @Autowired
public EndpointConfiguration(ApplicationProperties applicationProperties) { public EndpointConfiguration(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
init(); init();
processEnvironmentConfigs(); processEnvironmentConfigs();
} }
public void enableEndpoint(String endpoint) { public void enableEndpoint(String endpoint) {
endpointStatuses.put(endpoint, true); endpointStatuses.put(endpoint, true);
} }
public void disableEndpoint(String endpoint) { public void disableEndpoint(String endpoint) {
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
logger.info("Disabling {}", endpoint); logger.info("Disabling {}", endpoint);
endpointStatuses.put(endpoint, false); endpointStatuses.put(endpoint, false);
} }
} }
public boolean isEndpointEnabled(String endpoint) { public boolean isEndpointEnabled(String endpoint) {
if (endpoint.startsWith("/")) { if (endpoint.startsWith("/")) {
endpoint = endpoint.substring(1); endpoint = endpoint.substring(1);
} }
return endpointStatuses.getOrDefault(endpoint, true); return endpointStatuses.getOrDefault(endpoint, true);
} }
public void addEndpointToGroup(String group, String endpoint) { public void addEndpointToGroup(String group, String endpoint) {
endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint); endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint);
} }
public void enableGroup(String group) { public void enableGroup(String group) {
Set<String> endpoints = endpointGroups.get(group); Set<String> endpoints = endpointGroups.get(group);
if (endpoints != null) { if (endpoints != null) {
for (String endpoint : endpoints) { for (String endpoint : endpoints) {
enableEndpoint(endpoint); enableEndpoint(endpoint);
} }
} }
} }
public void disableGroup(String group) { public void disableGroup(String group) {
Set<String> endpoints = endpointGroups.get(group); Set<String> endpoints = endpointGroups.get(group);
if (endpoints != null) { if (endpoints != null) {
for (String endpoint : endpoints) { for (String endpoint : endpoints) {
disableEndpoint(endpoint); disableEndpoint(endpoint);
} }
} }
} }
public void init() { public void init() {
// Adding endpoints to "PageOps" group // Adding endpoints to "PageOps" group
addEndpointToGroup("PageOps", "remove-pages"); addEndpointToGroup("PageOps", "remove-pages");
addEndpointToGroup("PageOps", "merge-pdfs"); addEndpointToGroup("PageOps", "merge-pdfs");
addEndpointToGroup("PageOps", "split-pdfs"); addEndpointToGroup("PageOps", "split-pdfs");
addEndpointToGroup("PageOps", "pdf-organizer"); addEndpointToGroup("PageOps", "pdf-organizer");
addEndpointToGroup("PageOps", "rotate-pdf"); addEndpointToGroup("PageOps", "rotate-pdf");
addEndpointToGroup("PageOps", "multi-page-layout"); addEndpointToGroup("PageOps", "multi-page-layout");
addEndpointToGroup("PageOps", "scale-pages"); addEndpointToGroup("PageOps", "scale-pages");
addEndpointToGroup("PageOps", "adjust-contrast"); addEndpointToGroup("PageOps", "adjust-contrast");
addEndpointToGroup("PageOps", "crop"); addEndpointToGroup("PageOps", "crop");
addEndpointToGroup("PageOps", "auto-split-pdf"); addEndpointToGroup("PageOps", "auto-split-pdf");
addEndpointToGroup("PageOps", "extract-page"); addEndpointToGroup("PageOps", "extract-page");
addEndpointToGroup("PageOps", "pdf-to-single-page"); addEndpointToGroup("PageOps", "pdf-to-single-page");
addEndpointToGroup("PageOps", "split-by-size-or-count"); addEndpointToGroup("PageOps", "split-by-size-or-count");
addEndpointToGroup("PageOps", "overlay-pdf"); addEndpointToGroup("PageOps", "overlay-pdf");
addEndpointToGroup("PageOps", "split-pdf-by-sections"); addEndpointToGroup("PageOps", "split-pdf-by-sections");
// Adding endpoints to "Convert" group // Adding endpoints to "Convert" group
addEndpointToGroup("Convert", "pdf-to-img"); addEndpointToGroup("Convert", "pdf-to-img");
addEndpointToGroup("Convert", "img-to-pdf"); addEndpointToGroup("Convert", "img-to-pdf");
addEndpointToGroup("Convert", "pdf-to-pdfa"); addEndpointToGroup("Convert", "pdf-to-pdfa");
addEndpointToGroup("Convert", "file-to-pdf"); addEndpointToGroup("Convert", "file-to-pdf");
addEndpointToGroup("Convert", "xlsx-to-pdf"); addEndpointToGroup("Convert", "xlsx-to-pdf");
addEndpointToGroup("Convert", "pdf-to-word"); addEndpointToGroup("Convert", "pdf-to-word");
addEndpointToGroup("Convert", "pdf-to-presentation"); addEndpointToGroup("Convert", "pdf-to-presentation");
addEndpointToGroup("Convert", "pdf-to-text"); addEndpointToGroup("Convert", "pdf-to-text");
addEndpointToGroup("Convert", "pdf-to-html"); addEndpointToGroup("Convert", "pdf-to-html");
addEndpointToGroup("Convert", "pdf-to-xml"); addEndpointToGroup("Convert", "pdf-to-xml");
addEndpointToGroup("Convert", "html-to-pdf"); addEndpointToGroup("Convert", "html-to-pdf");
addEndpointToGroup("Convert", "url-to-pdf"); addEndpointToGroup("Convert", "url-to-pdf");
addEndpointToGroup("Convert", "markdown-to-pdf"); addEndpointToGroup("Convert", "markdown-to-pdf");
addEndpointToGroup("Convert", "pdf-to-csv"); addEndpointToGroup("Convert", "pdf-to-csv");
// Adding endpoints to "Security" group // Adding endpoints to "Security" group
addEndpointToGroup("Security", "add-password"); addEndpointToGroup("Security", "add-password");
addEndpointToGroup("Security", "remove-password"); addEndpointToGroup("Security", "remove-password");
addEndpointToGroup("Security", "change-permissions"); addEndpointToGroup("Security", "change-permissions");
addEndpointToGroup("Security", "add-watermark"); addEndpointToGroup("Security", "add-watermark");
addEndpointToGroup("Security", "cert-sign"); addEndpointToGroup("Security", "cert-sign");
addEndpointToGroup("Security", "sanitize-pdf"); addEndpointToGroup("Security", "sanitize-pdf");
addEndpointToGroup("Security", "auto-redact"); addEndpointToGroup("Security", "auto-redact");
// Adding endpoints to "Other" group // Adding endpoints to "Other" group
addEndpointToGroup("Other", "ocr-pdf"); addEndpointToGroup("Other", "ocr-pdf");
addEndpointToGroup("Other", "add-image"); addEndpointToGroup("Other", "add-image");
addEndpointToGroup("Other", "compress-pdf"); addEndpointToGroup("Other", "compress-pdf");
addEndpointToGroup("Other", "extract-images"); addEndpointToGroup("Other", "extract-images");
addEndpointToGroup("Other", "change-metadata"); addEndpointToGroup("Other", "change-metadata");
addEndpointToGroup("Other", "extract-image-scans"); addEndpointToGroup("Other", "extract-image-scans");
addEndpointToGroup("Other", "sign"); addEndpointToGroup("Other", "sign");
addEndpointToGroup("Other", "flatten"); addEndpointToGroup("Other", "flatten");
addEndpointToGroup("Other", "repair"); addEndpointToGroup("Other", "repair");
addEndpointToGroup("Other", "remove-blanks"); addEndpointToGroup("Other", "remove-blanks");
addEndpointToGroup("Other", "remove-annotations"); addEndpointToGroup("Other", "remove-annotations");
addEndpointToGroup("Other", "compare"); addEndpointToGroup("Other", "compare");
addEndpointToGroup("Other", "add-page-numbers"); addEndpointToGroup("Other", "add-page-numbers");
addEndpointToGroup("Other", "auto-rename"); addEndpointToGroup("Other", "auto-rename");
addEndpointToGroup("Other", "get-info-on-pdf"); addEndpointToGroup("Other", "get-info-on-pdf");
addEndpointToGroup("Other", "show-javascript"); addEndpointToGroup("Other", "show-javascript");
// CLI // CLI
addEndpointToGroup("CLI", "compress-pdf"); addEndpointToGroup("CLI", "compress-pdf");
addEndpointToGroup("CLI", "extract-image-scans"); addEndpointToGroup("CLI", "extract-image-scans");
addEndpointToGroup("CLI", "remove-blanks"); addEndpointToGroup("CLI", "remove-blanks");
addEndpointToGroup("CLI", "repair"); addEndpointToGroup("CLI", "repair");
addEndpointToGroup("CLI", "pdf-to-pdfa"); addEndpointToGroup("CLI", "pdf-to-pdfa");
addEndpointToGroup("CLI", "file-to-pdf"); addEndpointToGroup("CLI", "file-to-pdf");
addEndpointToGroup("CLI", "xlsx-to-pdf"); addEndpointToGroup("CLI", "xlsx-to-pdf");
addEndpointToGroup("CLI", "pdf-to-word"); addEndpointToGroup("CLI", "pdf-to-word");
addEndpointToGroup("CLI", "pdf-to-presentation"); addEndpointToGroup("CLI", "pdf-to-presentation");
addEndpointToGroup("CLI", "pdf-to-text"); addEndpointToGroup("CLI", "pdf-to-text");
addEndpointToGroup("CLI", "pdf-to-html"); addEndpointToGroup("CLI", "pdf-to-html");
addEndpointToGroup("CLI", "pdf-to-xml"); addEndpointToGroup("CLI", "pdf-to-xml");
addEndpointToGroup("CLI", "ocr-pdf"); addEndpointToGroup("CLI", "ocr-pdf");
addEndpointToGroup("CLI", "html-to-pdf"); addEndpointToGroup("CLI", "html-to-pdf");
addEndpointToGroup("CLI", "url-to-pdf"); addEndpointToGroup("CLI", "url-to-pdf");
// python // python
addEndpointToGroup("Python", "extract-image-scans"); addEndpointToGroup("Python", "extract-image-scans");
addEndpointToGroup("Python", "remove-blanks"); addEndpointToGroup("Python", "remove-blanks");
addEndpointToGroup("Python", "html-to-pdf"); addEndpointToGroup("Python", "html-to-pdf");
addEndpointToGroup("Python", "url-to-pdf"); addEndpointToGroup("Python", "url-to-pdf");
// openCV // openCV
addEndpointToGroup("OpenCV", "extract-image-scans"); addEndpointToGroup("OpenCV", "extract-image-scans");
addEndpointToGroup("OpenCV", "remove-blanks"); addEndpointToGroup("OpenCV", "remove-blanks");
// LibreOffice // LibreOffice
addEndpointToGroup("LibreOffice", "repair"); addEndpointToGroup("LibreOffice", "repair");
addEndpointToGroup("LibreOffice", "file-to-pdf"); addEndpointToGroup("LibreOffice", "file-to-pdf");
addEndpointToGroup("LibreOffice", "xlsx-to-pdf"); addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
addEndpointToGroup("LibreOffice", "pdf-to-word"); addEndpointToGroup("LibreOffice", "pdf-to-word");
addEndpointToGroup("LibreOffice", "pdf-to-presentation"); addEndpointToGroup("LibreOffice", "pdf-to-presentation");
addEndpointToGroup("LibreOffice", "pdf-to-text"); addEndpointToGroup("LibreOffice", "pdf-to-text");
addEndpointToGroup("LibreOffice", "pdf-to-html"); addEndpointToGroup("LibreOffice", "pdf-to-html");
addEndpointToGroup("LibreOffice", "pdf-to-xml"); addEndpointToGroup("LibreOffice", "pdf-to-xml");
// OCRmyPDF // OCRmyPDF
addEndpointToGroup("OCRmyPDF", "compress-pdf"); addEndpointToGroup("OCRmyPDF", "compress-pdf");
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa"); addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
addEndpointToGroup("OCRmyPDF", "ocr-pdf"); addEndpointToGroup("OCRmyPDF", "ocr-pdf");
// Java // Java
addEndpointToGroup("Java", "merge-pdfs"); addEndpointToGroup("Java", "merge-pdfs");
addEndpointToGroup("Java", "remove-pages"); addEndpointToGroup("Java", "remove-pages");
addEndpointToGroup("Java", "split-pdfs"); addEndpointToGroup("Java", "split-pdfs");
addEndpointToGroup("Java", "pdf-organizer"); addEndpointToGroup("Java", "pdf-organizer");
addEndpointToGroup("Java", "rotate-pdf"); addEndpointToGroup("Java", "rotate-pdf");
addEndpointToGroup("Java", "pdf-to-img"); addEndpointToGroup("Java", "pdf-to-img");
addEndpointToGroup("Java", "img-to-pdf"); addEndpointToGroup("Java", "img-to-pdf");
addEndpointToGroup("Java", "add-password"); addEndpointToGroup("Java", "add-password");
addEndpointToGroup("Java", "remove-password"); addEndpointToGroup("Java", "remove-password");
addEndpointToGroup("Java", "change-permissions"); addEndpointToGroup("Java", "change-permissions");
addEndpointToGroup("Java", "add-watermark"); addEndpointToGroup("Java", "add-watermark");
addEndpointToGroup("Java", "add-image"); addEndpointToGroup("Java", "add-image");
addEndpointToGroup("Java", "extract-images"); addEndpointToGroup("Java", "extract-images");
addEndpointToGroup("Java", "change-metadata"); addEndpointToGroup("Java", "change-metadata");
addEndpointToGroup("Java", "cert-sign"); addEndpointToGroup("Java", "cert-sign");
addEndpointToGroup("Java", "multi-page-layout"); addEndpointToGroup("Java", "multi-page-layout");
addEndpointToGroup("Java", "scale-pages"); addEndpointToGroup("Java", "scale-pages");
addEndpointToGroup("Java", "add-page-numbers"); addEndpointToGroup("Java", "add-page-numbers");
addEndpointToGroup("Java", "auto-rename"); addEndpointToGroup("Java", "auto-rename");
addEndpointToGroup("Java", "auto-split-pdf"); addEndpointToGroup("Java", "auto-split-pdf");
addEndpointToGroup("Java", "sanitize-pdf"); addEndpointToGroup("Java", "sanitize-pdf");
addEndpointToGroup("Java", "crop"); addEndpointToGroup("Java", "crop");
addEndpointToGroup("Java", "get-info-on-pdf"); addEndpointToGroup("Java", "get-info-on-pdf");
addEndpointToGroup("Java", "extract-page"); addEndpointToGroup("Java", "extract-page");
addEndpointToGroup("Java", "pdf-to-single-page"); addEndpointToGroup("Java", "pdf-to-single-page");
addEndpointToGroup("Java", "markdown-to-pdf"); addEndpointToGroup("Java", "markdown-to-pdf");
addEndpointToGroup("Java", "show-javascript"); addEndpointToGroup("Java", "show-javascript");
addEndpointToGroup("Java", "auto-redact"); addEndpointToGroup("Java", "auto-redact");
addEndpointToGroup("Java", "pdf-to-csv"); addEndpointToGroup("Java", "pdf-to-csv");
addEndpointToGroup("Java", "split-by-size-or-count"); addEndpointToGroup("Java", "split-by-size-or-count");
addEndpointToGroup("Java", "overlay-pdf"); addEndpointToGroup("Java", "overlay-pdf");
addEndpointToGroup("Java", "split-pdf-by-sections"); addEndpointToGroup("Java", "split-pdf-by-sections");
// Javascript // Javascript
addEndpointToGroup("Javascript", "pdf-organizer"); addEndpointToGroup("Javascript", "pdf-organizer");
addEndpointToGroup("Javascript", "sign"); addEndpointToGroup("Javascript", "sign");
addEndpointToGroup("Javascript", "compare"); addEndpointToGroup("Javascript", "compare");
addEndpointToGroup("Javascript", "adjust-contrast"); addEndpointToGroup("Javascript", "adjust-contrast");
} }
private void processEnvironmentConfigs() { private void processEnvironmentConfigs() {
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
if (endpointsToRemove != null) { if (endpointsToRemove != null) {
for (String endpoint : endpointsToRemove) { for (String endpoint : endpointsToRemove) {
disableEndpoint(endpoint.trim()); disableEndpoint(endpoint.trim());
} }
} }
if (groupsToRemove != null) { if (groupsToRemove != null) {
for (String group : groupsToRemove) { for (String group : groupsToRemove) {
disableGroup(group.trim()); disableGroup(group.trim());
} }
} }
} }
} }

View file

@ -1,26 +1,26 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@Component @Component
public class EndpointInterceptor implements HandlerInterceptor { public class EndpointInterceptor implements HandlerInterceptor {
@Autowired private EndpointConfiguration endpointConfiguration; @Autowired private EndpointConfiguration endpointConfiguration;
@Override @Override
public boolean preHandle( public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { throws Exception {
String requestURI = request.getRequestURI(); String requestURI = request.getRequestURI();
if (!endpointConfiguration.isEndpointEnabled(requestURI)) { if (!endpointConfiguration.isEndpointEnabled(requestURI)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled"); response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled");
return false; return false;
} }
return true; return true;
} }
} }

View file

@ -1,25 +1,25 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.config.MeterFilter; import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.config.MeterFilterReply; import io.micrometer.core.instrument.config.MeterFilterReply;
@Configuration @Configuration
public class MetricsConfig { public class MetricsConfig {
@Bean @Bean
public MeterFilter meterFilter() { public MeterFilter meterFilter() {
return new MeterFilter() { return new MeterFilter() {
@Override @Override
public MeterFilterReply accept(Meter.Id id) { public MeterFilterReply accept(Meter.Id id) {
if (id.getName().equals("http.requests")) { if (id.getName().equals("http.requests")) {
return MeterFilterReply.NEUTRAL; return MeterFilterReply.NEUTRAL;
} }
return MeterFilterReply.DENY; return MeterFilterReply.DENY;
} }
}; };
} }
} }

View file

@ -1,64 +1,64 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.IOException; import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@Component @Component
public class MetricsFilter extends OncePerRequestFilter { public class MetricsFilter extends OncePerRequestFilter {
private final MeterRegistry meterRegistry; private final MeterRegistry meterRegistry;
@Autowired @Autowired
public MetricsFilter(MeterRegistry meterRegistry) { public MetricsFilter(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry; this.meterRegistry = meterRegistry;
} }
@Override @Override
protected void doFilterInternal( protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
String uri = request.getRequestURI(); String uri = request.getRequestURI();
// System.out.println("uri="+uri + ", method=" + request.getMethod() ); // System.out.println("uri="+uri + ", method=" + request.getMethod() );
// Ignore static resources // Ignore static resources
if (!(uri.startsWith("/js") if (!(uri.startsWith("/js")
|| uri.startsWith("/v1/api-docs") || uri.startsWith("/v1/api-docs")
|| uri.endsWith("robots.txt") || uri.endsWith("robots.txt")
|| uri.startsWith("/images") || uri.startsWith("/images")
|| uri.startsWith("/images") || uri.startsWith("/images")
|| uri.endsWith(".png") || uri.endsWith(".png")
|| uri.endsWith(".ico") || uri.endsWith(".ico")
|| uri.endsWith(".css") || uri.endsWith(".css")
|| uri.endsWith(".map") || uri.endsWith(".map")
|| uri.endsWith(".svg") || uri.endsWith(".svg")
|| uri.endsWith(".js") || uri.endsWith(".js")
|| uri.contains("swagger") || uri.contains("swagger")
|| uri.startsWith("/api/v1/info") || uri.startsWith("/api/v1/info")
|| uri.startsWith("/site.webmanifest") || uri.startsWith("/site.webmanifest")
|| uri.startsWith("/fonts") || uri.startsWith("/fonts")
|| uri.startsWith("/pdfjs"))) { || uri.startsWith("/pdfjs"))) {
Counter counter = Counter counter =
Counter.builder("http.requests") Counter.builder("http.requests")
.tag("uri", uri) .tag("uri", uri)
.tag("method", request.getMethod()) .tag("method", request.getMethod())
.register(meterRegistry); .register(meterRegistry);
counter.increment(); counter.increment();
// System.out.println("Counted"); // System.out.println("Counted");
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
} }

View file

@ -1,53 +1,53 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.security.SecurityScheme;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Configuration @Configuration
public class OpenApiConfig { public class OpenApiConfig {
@Autowired ApplicationProperties applicationProperties; @Autowired ApplicationProperties applicationProperties;
@Bean @Bean
public OpenAPI customOpenAPI() { public OpenAPI customOpenAPI() {
String version = getClass().getPackage().getImplementationVersion(); String version = getClass().getPackage().getImplementationVersion();
if (version == null) { if (version == null) {
version = "1.0.0"; // default version if all else fails version = "1.0.0"; // default version if all else fails
} }
SecurityScheme apiKeyScheme = SecurityScheme apiKeyScheme =
new SecurityScheme() new SecurityScheme()
.type(SecurityScheme.Type.APIKEY) .type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER) .in(SecurityScheme.In.HEADER)
.name("X-API-KEY"); .name("X-API-KEY");
if (!applicationProperties.getSecurity().getEnableLogin()) { if (!applicationProperties.getSecurity().getEnableLogin()) {
return new OpenAPI() return new OpenAPI()
.components(new Components()) .components(new Components())
.info( .info(
new Info() new Info()
.title("Stirling PDF API") .title("Stirling PDF API")
.version(version) .version(version)
.description( .description(
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
} else { } else {
return new OpenAPI() return new OpenAPI()
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
.info( .info(
new Info() new Info()
.title("Stirling PDF API") .title("Stirling PDF API")
.version(version) .version(version)
.description( .description(
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")) "API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."))
.addSecurityItem(new SecurityRequirement().addList("apiKey")); .addSecurityItem(new SecurityRequirement().addList("apiKey"));
} }
} }
} }

View file

@ -1,18 +1,18 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> { public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
public static LocalDateTime startTime; public static LocalDateTime startTime;
@Override @Override
public void onApplicationEvent(ContextRefreshedEvent event) { public void onApplicationEvent(ContextRefreshedEvent event) {
startTime = LocalDateTime.now(); startTime = LocalDateTime.now();
} }
} }

View file

@ -1,26 +1,26 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class WebMvcConfig implements WebMvcConfigurer { public class WebMvcConfig implements WebMvcConfigurer {
@Autowired private EndpointInterceptor endpointInterceptor; @Autowired private EndpointInterceptor endpointInterceptor;
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(endpointInterceptor); registry.addInterceptor(endpointInterceptor);
} }
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Handler for external static resources // Handler for external static resources
registry.addResourceHandler("/**") registry.addResourceHandler("/**")
.addResourceLocations("file:customFiles/static/", "classpath:/static/"); .addResourceLocations("file:customFiles/static/", "classpath:/static/");
// .setCachePeriod(0); // Optional: disable caching // .setCachePeriod(0); // Optional: disable caching
} }
} }

View file

@ -1,49 +1,49 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import java.io.IOException; import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@Component @Component
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired private final LoginAttemptService loginAttemptService; @Autowired private final LoginAttemptService loginAttemptService;
@Autowired @Autowired
public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) { public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) {
this.loginAttemptService = loginAttemptService; this.loginAttemptService = loginAttemptService;
} }
@Override @Override
public void onAuthenticationFailure( public void onAuthenticationFailure(
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
AuthenticationException exception) AuthenticationException exception)
throws IOException, ServletException { throws IOException, ServletException {
String ip = request.getRemoteAddr(); String ip = request.getRemoteAddr();
logger.error("Failed login attempt from IP: " + ip); logger.error("Failed login attempt from IP: " + ip);
String username = request.getParameter("username"); String username = request.getParameter("username");
if (loginAttemptService.loginAttemptCheck(username)) { if (loginAttemptService.loginAttemptCheck(username)) {
setDefaultFailureUrl("/login?error=locked"); setDefaultFailureUrl("/login?error=locked");
} else { } else {
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) { if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
setDefaultFailureUrl("/login?error=badcredentials"); setDefaultFailureUrl("/login?error=badcredentials");
} else if (exception.getClass().isAssignableFrom(LockedException.class)) { } else if (exception.getClass().isAssignableFrom(LockedException.class)) {
setDefaultFailureUrl("/login?error=locked"); setDefaultFailureUrl("/login?error=locked");
} }
} }
super.onAuthenticationFailure(request, response, exception); super.onAuthenticationFailure(request, response, exception);
} }
} }

View file

@ -1,57 +1,57 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import stirling.software.SPDF.model.Authority; import stirling.software.SPDF.model.Authority;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
import stirling.software.SPDF.repository.UserRepository; import stirling.software.SPDF.repository.UserRepository;
@Service @Service
public class CustomUserDetailsService implements UserDetailsService { public class CustomUserDetailsService implements UserDetailsService {
@Autowired private UserRepository userRepository; @Autowired private UserRepository userRepository;
@Autowired private LoginAttemptService loginAttemptService; @Autowired private LoginAttemptService loginAttemptService;
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = User user =
userRepository userRepository
.findByUsername(username) .findByUsername(username)
.orElseThrow( .orElseThrow(
() -> () ->
new UsernameNotFoundException( new UsernameNotFoundException(
"No user found with username: " + username)); "No user found with username: " + username));
if (loginAttemptService.isBlocked(username)) { if (loginAttemptService.isBlocked(username)) {
throw new LockedException( throw new LockedException(
"Your account has been locked due to too many failed login attempts."); "Your account has been locked due to too many failed login attempts.");
} }
return new org.springframework.security.core.userdetails.User( return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getUsername(),
user.getPassword(), user.getPassword(),
user.isEnabled(), user.isEnabled(),
true, true,
true, true,
true, true,
getAuthorities(user.getAuthorities())); getAuthorities(user.getAuthorities()));
} }
private Collection<? extends GrantedAuthority> getAuthorities(Set<Authority> authorities) { private Collection<? extends GrantedAuthority> getAuthorities(Set<Authority> authorities) {
return authorities.stream() return authorities.stream()
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) .map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }

View file

@ -1,140 +1,140 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import stirling.software.SPDF.repository.JPATokenRepositoryImpl; import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
@Configuration @Configuration
@EnableWebSecurity() @EnableWebSecurity()
@EnableMethodSecurity @EnableMethodSecurity
public class SecurityConfiguration { public class SecurityConfiguration {
@Autowired private UserDetailsService userDetailsService; @Autowired private UserDetailsService userDetailsService;
@Bean @Bean
public PasswordEncoder passwordEncoder() { public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
@Autowired @Lazy private UserService userService; @Autowired @Lazy private UserService userService;
@Autowired @Autowired
@Qualifier("loginEnabled") @Qualifier("loginEnabled")
public boolean loginEnabledValue; public boolean loginEnabledValue;
@Autowired private UserAuthenticationFilter userAuthenticationFilter; @Autowired private UserAuthenticationFilter userAuthenticationFilter;
@Autowired private LoginAttemptService loginAttemptService; @Autowired private LoginAttemptService loginAttemptService;
@Autowired private FirstLoginFilter firstLoginFilter; @Autowired private FirstLoginFilter firstLoginFilter;
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
if (loginEnabledValue) { if (loginEnabledValue) {
http.csrf(csrf -> csrf.disable()); http.csrf(csrf -> csrf.disable());
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
http.formLogin( http.formLogin(
formLogin -> formLogin ->
formLogin formLogin
.loginPage("/login") .loginPage("/login")
.successHandler( .successHandler(
new CustomAuthenticationSuccessHandler()) new CustomAuthenticationSuccessHandler())
.defaultSuccessUrl("/") .defaultSuccessUrl("/")
.failureHandler( .failureHandler(
new CustomAuthenticationFailureHandler( new CustomAuthenticationFailureHandler(
loginAttemptService)) loginAttemptService))
.permitAll()) .permitAll())
.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())) .requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()))
.logout( .logout(
logout -> logout ->
logout.logoutRequestMatcher( logout.logoutRequestMatcher(
new AntPathRequestMatcher("/logout")) new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout=true") .logoutSuccessUrl("/login?logout=true")
.invalidateHttpSession(true) // Invalidate session .invalidateHttpSession(true) // Invalidate session
.deleteCookies("JSESSIONID", "remember-me")) .deleteCookies("JSESSIONID", "remember-me"))
.rememberMe( .rememberMe(
rememberMeConfigurer -> rememberMeConfigurer ->
rememberMeConfigurer // Use the configurator directly rememberMeConfigurer // Use the configurator directly
.key("uniqueAndSecret") .key("uniqueAndSecret")
.tokenRepository(persistentTokenRepository()) .tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(1209600) // 2 weeks .tokenValiditySeconds(1209600) // 2 weeks
) )
.authorizeHttpRequests( .authorizeHttpRequests(
authz -> authz ->
authz.requestMatchers( authz.requestMatchers(
req -> { req -> {
String uri = req.getRequestURI(); String uri = req.getRequestURI();
String contextPath = req.getContextPath(); String contextPath = req.getContextPath();
// Remove the context path from the URI // Remove the context path from the URI
String trimmedUri = String trimmedUri =
uri.startsWith(contextPath) uri.startsWith(contextPath)
? uri.substring( ? uri.substring(
contextPath contextPath
.length()) .length())
: uri; : uri;
return trimmedUri.startsWith("/login") return trimmedUri.startsWith("/login")
|| trimmedUri.endsWith(".svg") || trimmedUri.endsWith(".svg")
|| trimmedUri.startsWith( || trimmedUri.startsWith(
"/register") "/register")
|| trimmedUri.startsWith("/error") || trimmedUri.startsWith("/error")
|| trimmedUri.startsWith("/images/") || trimmedUri.startsWith("/images/")
|| trimmedUri.startsWith("/public/") || trimmedUri.startsWith("/public/")
|| trimmedUri.startsWith("/css/") || trimmedUri.startsWith("/css/")
|| trimmedUri.startsWith("/js/") || trimmedUri.startsWith("/js/")
|| trimmedUri.startsWith( || trimmedUri.startsWith(
"/api/v1/info/status"); "/api/v1/info/status");
}) })
.permitAll() .permitAll()
.anyRequest() .anyRequest()
.authenticated()) .authenticated())
.userDetailsService(userDetailsService) .userDetailsService(userDetailsService)
.authenticationProvider(authenticationProvider()); .authenticationProvider(authenticationProvider());
} else { } else {
http.csrf(csrf -> csrf.disable()) http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); .authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
} }
return http.build(); return http.build();
} }
@Bean @Bean
public IPRateLimitingFilter rateLimitingFilter() { public IPRateLimitingFilter rateLimitingFilter() {
int maxRequestsPerIp = 1000000; // Example limit TODO add config level int maxRequestsPerIp = 1000000; // Example limit TODO add config level
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp); return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
} }
@Bean @Bean
public DaoAuthenticationProvider authenticationProvider() { public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService); authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder()); authProvider.setPasswordEncoder(passwordEncoder());
return authProvider; return authProvider;
} }
@Bean @Bean
public PersistentTokenRepository persistentTokenRepository() { public PersistentTokenRepository persistentTokenRepository() {
return new JPATokenRepositoryImpl(); return new JPATokenRepositoryImpl();
} }
} }

View file

@ -1,118 +1,118 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import java.io.IOException; import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.model.ApiKeyAuthenticationToken; import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
@Component @Component
public class UserAuthenticationFilter extends OncePerRequestFilter { public class UserAuthenticationFilter extends OncePerRequestFilter {
@Autowired private UserDetailsService userDetailsService; @Autowired private UserDetailsService userDetailsService;
@Autowired @Lazy private UserService userService; @Autowired @Lazy private UserService userService;
@Autowired @Autowired
@Qualifier("loginEnabled") @Qualifier("loginEnabled")
public boolean loginEnabledValue; public boolean loginEnabledValue;
@Override @Override
protected void doFilterInternal( protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
if (!loginEnabledValue) { if (!loginEnabledValue) {
// If login is not enabled, just pass all requests without authentication // If login is not enabled, just pass all requests without authentication
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
return; return;
} }
String requestURI = request.getRequestURI(); String requestURI = request.getRequestURI();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// Check for API key in the request headers if no authentication exists // Check for API key in the request headers if no authentication exists
if (authentication == null || !authentication.isAuthenticated()) { if (authentication == null || !authentication.isAuthenticated()) {
String apiKey = request.getHeader("X-API-Key"); String apiKey = request.getHeader("X-API-Key");
if (apiKey != null && !apiKey.trim().isEmpty()) { if (apiKey != null && !apiKey.trim().isEmpty()) {
try { try {
// Use API key to authenticate. This requires you to have an authentication // Use API key to authenticate. This requires you to have an authentication
// provider for API keys. // provider for API keys.
UserDetails userDetails = userService.loadUserByApiKey(apiKey); UserDetails userDetails = userService.loadUserByApiKey(apiKey);
if (userDetails == null) { if (userDetails == null) {
response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("Invalid API Key."); response.getWriter().write("Invalid API Key.");
return; return;
} }
authentication = authentication =
new ApiKeyAuthenticationToken( new ApiKeyAuthenticationToken(
userDetails, apiKey, userDetails.getAuthorities()); userDetails, apiKey, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (AuthenticationException e) { } catch (AuthenticationException e) {
// If API key authentication fails, deny the request // If API key authentication fails, deny the request
response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("Invalid API Key."); response.getWriter().write("Invalid API Key.");
return; return;
} }
} }
} }
// If we still don't have any authentication, deny the request // If we still don't have any authentication, deny the request
if (authentication == null || !authentication.isAuthenticated()) { if (authentication == null || !authentication.isAuthenticated()) {
String method = request.getMethod(); String method = request.getMethod();
String contextPath = request.getContextPath(); String contextPath = request.getContextPath();
if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) { if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) {
response.sendRedirect(contextPath + "/login"); // redirect to the login page response.sendRedirect(contextPath + "/login"); // redirect to the login page
return; return;
} else { } else {
response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter() response.getWriter()
.write( .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"); "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; return;
} }
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
@Override @Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String uri = request.getRequestURI(); String uri = request.getRequestURI();
String contextPath = request.getContextPath(); String contextPath = request.getContextPath();
String[] permitAllPatterns = { String[] permitAllPatterns = {
contextPath + "/login", contextPath + "/login",
contextPath + "/register", contextPath + "/register",
contextPath + "/error", contextPath + "/error",
contextPath + "/images/", contextPath + "/images/",
contextPath + "/public/", contextPath + "/public/",
contextPath + "/css/", contextPath + "/css/",
contextPath + "/js/", contextPath + "/js/",
contextPath + "/pdfjs/", contextPath + "/pdfjs/",
contextPath + "/api/v1/info/status", contextPath + "/api/v1/info/status",
contextPath + "/site.webmanifest" contextPath + "/site.webmanifest"
}; };
for (String pattern : permitAllPatterns) { for (String pattern : permitAllPatterns) {
if (uri.startsWith(pattern) || uri.endsWith(".svg")) { if (uri.startsWith(pattern) || uri.endsWith(".svg")) {
return true; return true;
} }
} }
return false; return false;
} }
} }

View file

@ -1,143 +1,143 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket; import io.github.bucket4j.Bucket;
import io.github.bucket4j.ConsumptionProbe; import io.github.bucket4j.ConsumptionProbe;
import io.github.bucket4j.Refill; import io.github.bucket4j.Refill;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
@Component @Component
public class UserBasedRateLimitingFilter extends OncePerRequestFilter { public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>(); private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>();
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>(); private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>();
@Autowired private UserDetailsService userDetailsService; @Autowired private UserDetailsService userDetailsService;
@Autowired @Autowired
@Qualifier("rateLimit") @Qualifier("rateLimit")
public boolean rateLimit; public boolean rateLimit;
@Override @Override
protected void doFilterInternal( protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
if (!rateLimit) { if (!rateLimit) {
// If rateLimit is not enabled, just pass all requests without rate limiting // If rateLimit is not enabled, just pass all requests without rate limiting
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
return; return;
} }
String method = request.getMethod(); String method = request.getMethod();
if (!"POST".equalsIgnoreCase(method)) { if (!"POST".equalsIgnoreCase(method)) {
// If the request is not a POST, just pass it through without rate limiting // If the request is not a POST, just pass it through without rate limiting
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
return; return;
} }
String identifier = null; String identifier = null;
// Check for API key in the request headers // Check for API key in the request headers
String apiKey = request.getHeader("X-API-Key"); String apiKey = request.getHeader("X-API-Key");
if (apiKey != null && !apiKey.trim().isEmpty()) { if (apiKey != null && !apiKey.trim().isEmpty()) {
identifier = identifier =
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
} else { } else {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) { if (authentication != null && authentication.isAuthenticated()) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal(); UserDetails userDetails = (UserDetails) authentication.getPrincipal();
identifier = userDetails.getUsername(); identifier = userDetails.getUsername();
} }
} }
// If neither API key nor an authenticated user is present, use IP address // If neither API key nor an authenticated user is present, use IP address
if (identifier == null) { if (identifier == null) {
identifier = request.getRemoteAddr(); identifier = request.getRemoteAddr();
} }
Role userRole = Role userRole =
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
if (request.getHeader("X-API-Key") != null) { if (request.getHeader("X-API-Key") != null) {
// It's an API call // It's an API call
processRequest( processRequest(
userRole.getApiCallsPerDay(), userRole.getApiCallsPerDay(),
identifier, identifier,
apiBuckets, apiBuckets,
request, request,
response, response,
filterChain); filterChain);
} else { } else {
// It's a Web UI call // It's a Web UI call
processRequest( processRequest(
userRole.getWebCallsPerDay(), userRole.getWebCallsPerDay(),
identifier, identifier,
webBuckets, webBuckets,
request, request,
response, response,
filterChain); filterChain);
} }
} }
private Role getRoleFromAuthentication(Authentication authentication) { private Role getRoleFromAuthentication(Authentication authentication) {
if (authentication != null && authentication.isAuthenticated()) { if (authentication != null && authentication.isAuthenticated()) {
for (GrantedAuthority authority : authentication.getAuthorities()) { for (GrantedAuthority authority : authentication.getAuthorities()) {
try { try {
return Role.fromString(authority.getAuthority()); return Role.fromString(authority.getAuthority());
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
// Ignore and continue to next authority. // Ignore and continue to next authority.
} }
} }
} }
throw new IllegalStateException("User does not have a valid role."); throw new IllegalStateException("User does not have a valid role.");
} }
private void processRequest( private void processRequest(
int limitPerDay, int limitPerDay,
String identifier, String identifier,
Map<String, Bucket> buckets, Map<String, Bucket> buckets,
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
FilterChain filterChain) FilterChain filterChain)
throws IOException, ServletException { throws IOException, ServletException {
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay)); Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay));
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1); ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
if (probe.isConsumed()) { if (probe.isConsumed()) {
response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens())); response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens()));
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} else { } else {
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000; long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill)); response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill));
response.getWriter().write("Rate limit exceeded for POST requests."); response.getWriter().write("Rate limit exceeded for POST requests.");
} }
} }
private Bucket createUserBucket(int limitPerDay) { private Bucket createUserBucket(int limitPerDay) {
Bandwidth limit = Bandwidth limit =
Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1))); Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1)));
return Bucket.builder().addLimit(limit).build(); return Bucket.builder().addLimit(limit).build();
} }
} }

View file

@ -1,197 +1,197 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.Authority; import stirling.software.SPDF.model.Authority;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
import stirling.software.SPDF.repository.UserRepository; import stirling.software.SPDF.repository.UserRepository;
@Service @Service
public class UserService implements UserServiceInterface { public class UserService implements UserServiceInterface {
@Autowired private UserRepository userRepository; @Autowired private UserRepository userRepository;
@Autowired private PasswordEncoder passwordEncoder; @Autowired private PasswordEncoder passwordEncoder;
public Authentication getAuthentication(String apiKey) { public Authentication getAuthentication(String apiKey) {
User user = getUserByApiKey(apiKey); User user = getUserByApiKey(apiKey);
if (user == null) { if (user == null) {
throw new UsernameNotFoundException("API key is not valid"); throw new UsernameNotFoundException("API key is not valid");
} }
// Convert the user into an Authentication object // Convert the user into an Authentication object
return new UsernamePasswordAuthenticationToken( return new UsernamePasswordAuthenticationToken(
user, // principal (typically the user) user, // principal (typically the user)
null, // credentials (we don't expose the password or API key here) null, // credentials (we don't expose the password or API key here)
getAuthorities(user) // user's authorities (roles/permissions) getAuthorities(user) // user's authorities (roles/permissions)
); );
} }
private Collection<? extends GrantedAuthority> getAuthorities(User user) { private Collection<? extends GrantedAuthority> getAuthorities(User user) {
// Convert each Authority object into a SimpleGrantedAuthority object. // Convert each Authority object into a SimpleGrantedAuthority object.
return user.getAuthorities().stream() return user.getAuthorities().stream()
.map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority())) .map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private String generateApiKey() { private String generateApiKey() {
String apiKey; String apiKey;
do { do {
apiKey = UUID.randomUUID().toString(); apiKey = UUID.randomUUID().toString();
} while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness } while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness
return apiKey; return apiKey;
} }
public User addApiKeyToUser(String username) { public User addApiKeyToUser(String username) {
User user = User user =
userRepository userRepository
.findByUsername(username) .findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found")); .orElseThrow(() -> new UsernameNotFoundException("User not found"));
user.setApiKey(generateApiKey()); user.setApiKey(generateApiKey());
return userRepository.save(user); return userRepository.save(user);
} }
public User refreshApiKeyForUser(String username) { public User refreshApiKeyForUser(String username) {
return addApiKeyToUser(username); // reuse the add API key method for refreshing return addApiKeyToUser(username); // reuse the add API key method for refreshing
} }
public String getApiKeyForUser(String username) { public String getApiKeyForUser(String username) {
User user = User user =
userRepository userRepository
.findByUsername(username) .findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found")); .orElseThrow(() -> new UsernameNotFoundException("User not found"));
return user.getApiKey(); return user.getApiKey();
} }
public boolean isValidApiKey(String apiKey) { public boolean isValidApiKey(String apiKey) {
return userRepository.findByApiKey(apiKey) != null; return userRepository.findByApiKey(apiKey) != null;
} }
public User getUserByApiKey(String apiKey) { public User getUserByApiKey(String apiKey) {
return userRepository.findByApiKey(apiKey); return userRepository.findByApiKey(apiKey);
} }
public UserDetails loadUserByApiKey(String apiKey) { public UserDetails loadUserByApiKey(String apiKey) {
User userOptional = userRepository.findByApiKey(apiKey); User userOptional = userRepository.findByApiKey(apiKey);
if (userOptional != null) { if (userOptional != null) {
User user = userOptional; User user = userOptional;
// Convert your User entity to a UserDetails object with authorities // Convert your User entity to a UserDetails object with authorities
return new org.springframework.security.core.userdetails.User( return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getUsername(),
user.getPassword(), // you might not need this for API key auth user.getPassword(), // you might not need this for API key auth
getAuthorities(user)); getAuthorities(user));
} }
return null; // or throw an exception return null; // or throw an exception
} }
public boolean validateApiKeyForUser(String username, String apiKey) { public boolean validateApiKeyForUser(String username, String apiKey) {
Optional<User> userOpt = userRepository.findByUsername(username); Optional<User> userOpt = userRepository.findByUsername(username);
return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey); return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey);
} }
public void saveUser(String username, String password) { public void saveUser(String username, String password) {
User user = new User(); User user = new User();
user.setUsername(username); user.setUsername(username);
user.setPassword(passwordEncoder.encode(password)); user.setPassword(passwordEncoder.encode(password));
user.setEnabled(true); user.setEnabled(true);
userRepository.save(user); userRepository.save(user);
} }
public void saveUser(String username, String password, String role, boolean firstLogin) { public void saveUser(String username, String password, String role, boolean firstLogin) {
User user = new User(); User user = new User();
user.setUsername(username); user.setUsername(username);
user.setPassword(passwordEncoder.encode(password)); user.setPassword(passwordEncoder.encode(password));
user.addAuthority(new Authority(role, user)); user.addAuthority(new Authority(role, user));
user.setEnabled(true); user.setEnabled(true);
user.setFirstLogin(firstLogin); user.setFirstLogin(firstLogin);
userRepository.save(user); userRepository.save(user);
} }
public void saveUser(String username, String password, String role) { public void saveUser(String username, String password, String role) {
User user = new User(); User user = new User();
user.setUsername(username); user.setUsername(username);
user.setPassword(passwordEncoder.encode(password)); user.setPassword(passwordEncoder.encode(password));
user.addAuthority(new Authority(role, user)); user.addAuthority(new Authority(role, user));
user.setEnabled(true); user.setEnabled(true);
user.setFirstLogin(false); user.setFirstLogin(false);
userRepository.save(user); userRepository.save(user);
} }
public void deleteUser(String username) { public void deleteUser(String username) {
Optional<User> userOpt = userRepository.findByUsername(username); Optional<User> userOpt = userRepository.findByUsername(username);
if (userOpt.isPresent()) { if (userOpt.isPresent()) {
for (Authority authority : userOpt.get().getAuthorities()) { for (Authority authority : userOpt.get().getAuthorities()) {
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
return; return;
} }
} }
userRepository.delete(userOpt.get()); userRepository.delete(userOpt.get());
} }
} }
public boolean usernameExists(String username) { public boolean usernameExists(String username) {
return userRepository.findByUsername(username).isPresent(); return userRepository.findByUsername(username).isPresent();
} }
public boolean hasUsers() { public boolean hasUsers() {
return userRepository.count() > 0; return userRepository.count() > 0;
} }
public void updateUserSettings(String username, Map<String, String> updates) { public void updateUserSettings(String username, Map<String, String> updates) {
Optional<User> userOpt = userRepository.findByUsername(username); Optional<User> userOpt = userRepository.findByUsername(username);
if (userOpt.isPresent()) { if (userOpt.isPresent()) {
User user = userOpt.get(); User user = userOpt.get();
Map<String, String> settingsMap = user.getSettings(); Map<String, String> settingsMap = user.getSettings();
if (settingsMap == null) { if (settingsMap == null) {
settingsMap = new HashMap<String, String>(); settingsMap = new HashMap<String, String>();
} }
settingsMap.clear(); settingsMap.clear();
settingsMap.putAll(updates); settingsMap.putAll(updates);
user.setSettings(settingsMap); user.setSettings(settingsMap);
userRepository.save(user); userRepository.save(user);
} }
} }
public Optional<User> findByUsername(String username) { public Optional<User> findByUsername(String username) {
return userRepository.findByUsername(username); return userRepository.findByUsername(username);
} }
public void changeUsername(User user, String newUsername) { public void changeUsername(User user, String newUsername) {
user.setUsername(newUsername); user.setUsername(newUsername);
userRepository.save(user); userRepository.save(user);
} }
public void changePassword(User user, String newPassword) { public void changePassword(User user, String newPassword) {
user.setPassword(passwordEncoder.encode(newPassword)); user.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(user); userRepository.save(user);
} }
public void changeFirstUse(User user, boolean firstUse) { public void changeFirstUse(User user, boolean firstUse) {
user.setFirstLogin(firstUse); user.setFirstLogin(firstUse);
userRepository.save(user); userRepository.save(user);
} }
public boolean isPasswordCorrect(User user, String currentPassword) { public boolean isPasswordCorrect(User user, String currentPassword) {
return passwordEncoder.matches(currentPassword, user.getPassword()); return passwordEncoder.matches(currentPassword, user.getPassword());
} }
} }

View file

@ -119,11 +119,10 @@ public class SplitPdfBySectionsController {
// Set clipping area and position // Set clipping area and position
float translateX = -subPageWidth * i; float translateX = -subPageWidth * i;
float translateY = height - subPageHeight * (verticalDivisions - j); float translateY = height - subPageHeight * (verticalDivisions - j);
// Code for google Docs pdfs..
//Code for google Docs pdfs.. // float translateY = -subPageHeight * (verticalDivisions - 1 - j);
//float translateY = -subPageHeight * (verticalDivisions - 1 - j);
contentStream.saveGraphicsState(); contentStream.saveGraphicsState();
contentStream.addRect(0, 0, subPageWidth, subPageHeight); contentStream.addRect(0, 0, subPageWidth, subPageHeight);
contentStream.clip(); contentStream.clip();

View file

@ -1,77 +1,77 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@Tag(name = "Convert", description = "Convert APIs") @Tag(name = "Convert", description = "Convert APIs")
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
public class ConvertWebsiteToPDF { public class ConvertWebsiteToPDF {
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf") @PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
@Operation( @Operation(
summary = "Convert a URL to a PDF", summary = "Convert a URL to a PDF",
description = description =
"This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO") "This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO")
public ResponseEntity<byte[]> urlToPdf(@ModelAttribute UrlToPdfRequest request) public ResponseEntity<byte[]> urlToPdf(@ModelAttribute UrlToPdfRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
String URL = request.getUrlInput(); String URL = request.getUrlInput();
// Validate the URL format // Validate the URL format
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) { if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
throw new IllegalArgumentException("Invalid URL format provided."); throw new IllegalArgumentException("Invalid URL format provided.");
} }
Path tempOutputFile = null; Path tempOutputFile = null;
byte[] pdfBytes; byte[] pdfBytes;
try { try {
// Prepare the output file path // Prepare the output file path
tempOutputFile = Files.createTempFile("output_", ".pdf"); tempOutputFile = Files.createTempFile("output_", ".pdf");
// Prepare the OCRmyPDF command // Prepare the OCRmyPDF command
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
command.add("weasyprint"); command.add("weasyprint");
command.add(URL); command.add(URL);
command.add(tempOutputFile.toString()); command.add(tempOutputFile.toString());
ProcessExecutorResult returnCode = ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
.runCommandWithOutputHandling(command); .runCommandWithOutputHandling(command);
// Read the optimized PDF file // Read the optimized PDF file
pdfBytes = Files.readAllBytes(tempOutputFile); pdfBytes = Files.readAllBytes(tempOutputFile);
} finally { } finally {
// Clean up the temporary files // Clean up the temporary files
Files.delete(tempOutputFile); Files.delete(tempOutputFile);
} }
// Convert URL to a safe filename // Convert URL to a safe filename
String outputFilename = convertURLToFileName(URL); String outputFilename = convertURLToFileName(URL);
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
private String convertURLToFileName(String url) { private String convertURLToFileName(String url) {
String safeName = url.replaceAll("[^a-zA-Z0-9]", "_"); String safeName = url.replaceAll("[^a-zA-Z0-9]", "_");
if (safeName.length() > 50) { if (safeName.length() > 50) {
safeName = safeName.substring(0, 50); // restrict to 50 characters safeName = safeName.substring(0, 50); // restrict to 50 characters
} }
return safeName + ".pdf"; return safeName + ".pdf";
} }
} }

View file

@ -1,210 +1,210 @@
package stirling.software.SPDF.controller.api.filters; package stirling.software.SPDF.controller.api.filters;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.PDFComparisonAndCount; import stirling.software.SPDF.model.api.PDFComparisonAndCount;
import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.filter.ContainsTextRequest; import stirling.software.SPDF.model.api.filter.ContainsTextRequest;
import stirling.software.SPDF.model.api.filter.FileSizeRequest; import stirling.software.SPDF.model.api.filter.FileSizeRequest;
import stirling.software.SPDF.model.api.filter.PageRotationRequest; import stirling.software.SPDF.model.api.filter.PageRotationRequest;
import stirling.software.SPDF.model.api.filter.PageSizeRequest; import stirling.software.SPDF.model.api.filter.PageSizeRequest;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/filter") @RequestMapping("/api/v1/filter")
@Tag(name = "Filter", description = "Filter APIs") @Tag(name = "Filter", description = "Filter APIs")
public class FilterController { public class FilterController {
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text") @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
@Operation( @Operation(
summary = "Checks if a PDF contains set text, returns true if does", summary = "Checks if a PDF contains set text, returns true if does",
description = "Input:PDF Output:Boolean Type:SISO") description = "Input:PDF Output:Boolean Type:SISO")
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request) public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String text = request.getText(); String text = request.getText();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
if (PdfUtils.hasText(pdfDocument, pageNumber, text)) if (PdfUtils.hasText(pdfDocument, pageNumber, text))
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
pdfDocument, inputFile.getOriginalFilename()); pdfDocument, inputFile.getOriginalFilename());
return null; return null;
} }
// TODO // TODO
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image") @PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
@Operation( @Operation(
summary = "Checks if a PDF contains an image", summary = "Checks if a PDF contains an image",
description = "Input:PDF Output:Boolean Type:SISO") description = "Input:PDF Output:Boolean Type:SISO")
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request) public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
if (PdfUtils.hasImages(pdfDocument, pageNumber)) if (PdfUtils.hasImages(pdfDocument, pageNumber))
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
pdfDocument, inputFile.getOriginalFilename()); pdfDocument, inputFile.getOriginalFilename());
return null; return null;
} }
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count") @PostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
@Operation( @Operation(
summary = "Checks if a PDF is greater, less or equal to a setPageCount", summary = "Checks if a PDF is greater, less or equal to a setPageCount",
description = "Input:PDF Output:Boolean Type:SISO") description = "Input:PDF Output:Boolean Type:SISO")
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request) public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String pageCount = request.getPageCount(); String pageCount = request.getPageCount();
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = PDDocument.load(inputFile.getInputStream()); PDDocument document = PDDocument.load(inputFile.getInputStream());
int actualPageCount = document.getNumberOfPages(); int actualPageCount = document.getNumberOfPages();
boolean valid = false; boolean valid = false;
// Perform the comparison // Perform the comparison
switch (comparator) { switch (comparator) {
case "Greater": case "Greater":
valid = actualPageCount > Integer.parseInt(pageCount); valid = actualPageCount > Integer.parseInt(pageCount);
break; break;
case "Equal": case "Equal":
valid = actualPageCount == Integer.parseInt(pageCount); valid = actualPageCount == Integer.parseInt(pageCount);
break; break;
case "Less": case "Less":
valid = actualPageCount < Integer.parseInt(pageCount); valid = actualPageCount < Integer.parseInt(pageCount);
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw new IllegalArgumentException("Invalid comparator: " + comparator);
} }
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
return null; return null;
} }
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size") @PostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
@Operation( @Operation(
summary = "Checks if a PDF is of a certain size", summary = "Checks if a PDF is of a certain size",
description = "Input:PDF Output:Boolean Type:SISO") description = "Input:PDF Output:Boolean Type:SISO")
public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request) public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String standardPageSize = request.getStandardPageSize(); String standardPageSize = request.getStandardPageSize();
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = PDDocument.load(inputFile.getInputStream()); PDDocument document = PDDocument.load(inputFile.getInputStream());
PDPage firstPage = document.getPage(0); PDPage firstPage = document.getPage(0);
PDRectangle actualPageSize = firstPage.getMediaBox(); PDRectangle actualPageSize = firstPage.getMediaBox();
// Calculate the area of the actual page size // Calculate the area of the actual page size
float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight(); float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
// Get the standard size and calculate its area // Get the standard size and calculate its area
PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize); PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
float standardArea = standardSize.getWidth() * standardSize.getHeight(); float standardArea = standardSize.getWidth() * standardSize.getHeight();
boolean valid = false; boolean valid = false;
// Perform the comparison // Perform the comparison
switch (comparator) { switch (comparator) {
case "Greater": case "Greater":
valid = actualArea > standardArea; valid = actualArea > standardArea;
break; break;
case "Equal": case "Equal":
valid = actualArea == standardArea; valid = actualArea == standardArea;
break; break;
case "Less": case "Less":
valid = actualArea < standardArea; valid = actualArea < standardArea;
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw new IllegalArgumentException("Invalid comparator: " + comparator);
} }
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
return null; return null;
} }
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size") @PostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
@Operation( @Operation(
summary = "Checks if a PDF is a set file size", summary = "Checks if a PDF is a set file size",
description = "Input:PDF Output:Boolean Type:SISO") description = "Input:PDF Output:Boolean Type:SISO")
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request) public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String fileSize = request.getFileSize(); String fileSize = request.getFileSize();
String comparator = request.getComparator(); String comparator = request.getComparator();
// Get the file size // Get the file size
long actualFileSize = inputFile.getSize(); long actualFileSize = inputFile.getSize();
boolean valid = false; boolean valid = false;
// Perform the comparison // Perform the comparison
switch (comparator) { switch (comparator) {
case "Greater": case "Greater":
valid = actualFileSize > Long.parseLong(fileSize); valid = actualFileSize > Long.parseLong(fileSize);
break; break;
case "Equal": case "Equal":
valid = actualFileSize == Long.parseLong(fileSize); valid = actualFileSize == Long.parseLong(fileSize);
break; break;
case "Less": case "Less":
valid = actualFileSize < Long.parseLong(fileSize); valid = actualFileSize < Long.parseLong(fileSize);
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw new IllegalArgumentException("Invalid comparator: " + comparator);
} }
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
return null; return null;
} }
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation") @PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
@Operation( @Operation(
summary = "Checks if a PDF is of a certain rotation", summary = "Checks if a PDF is of a certain rotation",
description = "Input:PDF Output:Boolean Type:SISO") description = "Input:PDF Output:Boolean Type:SISO")
public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request) public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request)
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
int rotation = request.getRotation(); int rotation = request.getRotation();
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = PDDocument.load(inputFile.getInputStream()); PDDocument document = PDDocument.load(inputFile.getInputStream());
// Get the rotation of the first page // Get the rotation of the first page
PDPage firstPage = document.getPage(0); PDPage firstPage = document.getPage(0);
int actualRotation = firstPage.getRotation(); int actualRotation = firstPage.getRotation();
boolean valid = false; boolean valid = false;
// Perform the comparison // Perform the comparison
switch (comparator) { switch (comparator) {
case "Greater": case "Greater":
valid = actualRotation > rotation; valid = actualRotation > rotation;
break; break;
case "Equal": case "Equal":
valid = actualRotation == rotation; valid = actualRotation == rotation;
break; break;
case "Less": case "Less":
valid = actualRotation < rotation; valid = actualRotation < rotation;
break; break;
default: default:
throw new IllegalArgumentException("Invalid comparator: " + comparator); throw new IllegalArgumentException("Invalid comparator: " + comparator);
} }
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
return null; return null;
} }
} }

View file

@ -1,150 +1,150 @@
package stirling.software.SPDF.controller.api.misc; package stirling.software.SPDF.controller.api.misc;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest; import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/misc") @RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
public class PageNumbersController { public class PageNumbersController {
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") @PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Add page numbers to a PDF document", summary = "Add page numbers to a PDF document",
description = description =
"This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO") "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request) public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
throws IOException { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String customMargin = request.getCustomMargin(); String customMargin = request.getCustomMargin();
int position = request.getPosition(); int position = request.getPosition();
int startingNumber = request.getStartingNumber(); int startingNumber = request.getStartingNumber();
String pagesToNumber = request.getPagesToNumber(); String pagesToNumber = request.getPagesToNumber();
String customText = request.getCustomText(); String customText = request.getCustomText();
int pageNumber = startingNumber; int pageNumber = startingNumber;
byte[] fileBytes = file.getBytes(); byte[] fileBytes = file.getBytes();
PDDocument document = PDDocument.load(fileBytes); PDDocument document = PDDocument.load(fileBytes);
float marginFactor; float marginFactor;
switch (customMargin.toLowerCase()) { switch (customMargin.toLowerCase()) {
case "small": case "small":
marginFactor = 0.02f; marginFactor = 0.02f;
break; break;
case "medium": case "medium":
marginFactor = 0.035f; marginFactor = 0.035f;
break; break;
case "large": case "large":
marginFactor = 0.05f; marginFactor = 0.05f;
break; break;
case "x-large": case "x-large":
marginFactor = 0.075f; marginFactor = 0.075f;
break; break;
default: default:
marginFactor = 0.035f; marginFactor = 0.035f;
break; break;
} }
float fontSize = 12.0f; float fontSize = 12.0f;
PDType1Font font = PDType1Font.HELVETICA; PDType1Font font = PDType1Font.HELVETICA;
if (pagesToNumber == null || pagesToNumber.length() == 0) { if (pagesToNumber == null || pagesToNumber.length() == 0) {
pagesToNumber = "all"; pagesToNumber = "all";
} }
if (customText == null || customText.length() == 0) { if (customText == null || customText.length() == 0) {
customText = "{n}"; customText = "{n}";
} }
List<Integer> pagesToNumberList = List<Integer> pagesToNumberList =
GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages()); GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages());
for (int i : pagesToNumberList) { for (int i : pagesToNumberList) {
PDPage page = document.getPage(i); PDPage page = document.getPage(i);
PDRectangle pageSize = page.getMediaBox(); PDRectangle pageSize = page.getMediaBox();
String text = String text =
customText != null customText != null
? customText ? customText
.replace("{n}", String.valueOf(pageNumber)) .replace("{n}", String.valueOf(pageNumber))
.replace("{total}", String.valueOf(document.getNumberOfPages())) .replace("{total}", String.valueOf(document.getNumberOfPages()))
.replace( .replace(
"{filename}", "{filename}",
file.getOriginalFilename() file.getOriginalFilename()
.replaceFirst("[.][^.]+$", "")) .replaceFirst("[.][^.]+$", ""))
: String.valueOf(pageNumber); : String.valueOf(pageNumber);
float x, y; float x, y;
int xGroup = (position - 1) % 3; int xGroup = (position - 1) % 3;
int yGroup = 2 - (position - 1) / 3; int yGroup = 2 - (position - 1) / 3;
switch (xGroup) { switch (xGroup) {
case 0: // left case 0: // left
x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth(); x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
break; break;
case 1: // center case 1: // center
x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
break; break;
default: // right default: // right
x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth(); x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();
break; break;
} }
switch (yGroup) { switch (yGroup) {
case 0: // bottom case 0: // bottom
y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight(); y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
break; break;
case 1: // middle case 1: // middle
y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
break; break;
default: // top default: // top
y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight(); y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();
break; break;
} }
PDPageContentStream contentStream = PDPageContentStream contentStream =
new PDPageContentStream( new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true); document, page, PDPageContentStream.AppendMode.APPEND, true);
contentStream.beginText(); contentStream.beginText();
contentStream.setFont(font, fontSize); contentStream.setFont(font, fontSize);
contentStream.newLineAtOffset(x, y); contentStream.newLineAtOffset(x, y);
contentStream.showText(text); contentStream.showText(text);
contentStream.endText(); contentStream.endText();
contentStream.close(); contentStream.close();
pageNumber++; pageNumber++;
} }
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos); document.save(baos);
document.close(); document.close();
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(), baos.toByteArray(),
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf",
MediaType.APPLICATION_PDF); MediaType.APPLICATION_PDF);
} }
} }

View file

@ -1,133 +1,133 @@
package stirling.software.SPDF.controller.api.pipeline; package stirling.software.SPDF.controller.api.pipeline;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.api.HandleDataRequest; import stirling.software.SPDF.model.api.HandleDataRequest;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/pipeline") @RequestMapping("/api/v1/pipeline")
@Tag(name = "Pipeline", description = "Pipeline APIs") @Tag(name = "Pipeline", description = "Pipeline APIs")
public class PipelineController { public class PipelineController {
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class); private static final Logger logger = LoggerFactory.getLogger(PipelineController.class);
final String watchedFoldersDir = "./pipeline/watchedFolders/"; final String watchedFoldersDir = "./pipeline/watchedFolders/";
final String finishedFoldersDir = "./pipeline/finishedFolders/"; final String finishedFoldersDir = "./pipeline/finishedFolders/";
@Autowired PipelineProcessor processor; @Autowired PipelineProcessor processor;
@Autowired ApplicationProperties applicationProperties; @Autowired ApplicationProperties applicationProperties;
@Autowired private ObjectMapper objectMapper; @Autowired private ObjectMapper objectMapper;
@PostMapping("/handleData") @PostMapping("/handleData")
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request) public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
throws JsonMappingException, JsonProcessingException { throws JsonMappingException, JsonProcessingException {
if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) { if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} }
MultipartFile[] files = request.getFileInput(); MultipartFile[] files = request.getFileInput();
String jsonString = request.getJson(); String jsonString = request.getJson();
if (files == null) { if (files == null) {
return null; return null;
} }
PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class); PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class);
logger.info("Received POST request to /handleData with {} files", files.length); logger.info("Received POST request to /handleData with {} files", files.length);
try { try {
List<Resource> inputFiles = processor.generateInputFiles(files); List<Resource> inputFiles = processor.generateInputFiles(files);
if (inputFiles == null || inputFiles.size() == 0) { if (inputFiles == null || inputFiles.size() == 0) {
return null; return null;
} }
List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config); List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config);
if (outputFiles != null && outputFiles.size() == 1) { if (outputFiles != null && outputFiles.size() == 1) {
// If there is only one file, return it directly // If there is only one file, return it directly
Resource singleFile = outputFiles.get(0); Resource singleFile = outputFiles.get(0);
InputStream is = singleFile.getInputStream(); InputStream is = singleFile.getInputStream();
byte[] bytes = new byte[(int) singleFile.contentLength()]; byte[] bytes = new byte[(int) singleFile.contentLength()];
is.read(bytes); is.read(bytes);
is.close(); is.close();
logger.info("Returning single file response..."); logger.info("Returning single file response...");
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM); bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM);
} else if (outputFiles == null) { } else if (outputFiles == null) {
return null; return null;
} }
// Create a ByteArrayOutputStream to hold the zip // Create a ByteArrayOutputStream to hold the zip
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zipOut = new ZipOutputStream(baos); ZipOutputStream zipOut = new ZipOutputStream(baos);
// A map to keep track of filenames and their counts // A map to keep track of filenames and their counts
Map<String, Integer> filenameCount = new HashMap<>(); Map<String, Integer> filenameCount = new HashMap<>();
// Loop through each file and add it to the zip // Loop through each file and add it to the zip
for (Resource file : outputFiles) { for (Resource file : outputFiles) {
String originalFilename = file.getFilename(); String originalFilename = file.getFilename();
String filename = originalFilename; String filename = originalFilename;
// Check if the filename already exists, and modify it if necessary // Check if the filename already exists, and modify it if necessary
if (filenameCount.containsKey(originalFilename)) { if (filenameCount.containsKey(originalFilename)) {
int count = filenameCount.get(originalFilename); int count = filenameCount.get(originalFilename);
String baseName = originalFilename.replaceAll("\\.[^.]*$", ""); String baseName = originalFilename.replaceAll("\\.[^.]*$", "");
String extension = originalFilename.replaceAll("^.*\\.", ""); String extension = originalFilename.replaceAll("^.*\\.", "");
filename = baseName + "(" + count + ")." + extension; filename = baseName + "(" + count + ")." + extension;
filenameCount.put(originalFilename, count + 1); filenameCount.put(originalFilename, count + 1);
} else { } else {
filenameCount.put(originalFilename, 1); filenameCount.put(originalFilename, 1);
} }
ZipEntry zipEntry = new ZipEntry(filename); ZipEntry zipEntry = new ZipEntry(filename);
zipOut.putNextEntry(zipEntry); zipOut.putNextEntry(zipEntry);
// Read the file into a byte array // Read the file into a byte array
InputStream is = file.getInputStream(); InputStream is = file.getInputStream();
byte[] bytes = new byte[(int) file.contentLength()]; byte[] bytes = new byte[(int) file.contentLength()];
is.read(bytes); is.read(bytes);
// Write the bytes of the file to the zip // Write the bytes of the file to the zip
zipOut.write(bytes, 0, bytes.length); zipOut.write(bytes, 0, bytes.length);
zipOut.closeEntry(); zipOut.closeEntry();
is.close(); is.close();
} }
zipOut.close(); zipOut.close();
logger.info("Returning zipped file response..."); logger.info("Returning zipped file response...");
return WebResponseUtils.boasToWebResponse( return WebResponseUtils.boasToWebResponse(
baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM); baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM);
} catch (Exception e) { } catch (Exception e) {
logger.error("Error handling data: ", e); logger.error("Error handling data: ", e);
return null; return null;
} }
} }
} }

View file

@ -1,171 +1,171 @@
package stirling.software.SPDF.controller.api.security; package stirling.software.SPDF.controller.api.security;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDMetadata; import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.pdfbox.pdmodel.interactive.action.PDAction; import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch; import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions; import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.SanitizePdfRequest; import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/security") @RequestMapping("/api/v1/security")
@Tag(name = "Security", description = "Security APIs") @Tag(name = "Security", description = "Security APIs")
public class SanitizeController { public class SanitizeController {
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") @PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
@Operation( @Operation(
summary = "Sanitize a PDF file", summary = "Sanitize a PDF file",
description = description =
"This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO") "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request) public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
throws IOException { throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
boolean removeJavaScript = request.isRemoveJavaScript(); boolean removeJavaScript = request.isRemoveJavaScript();
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles(); boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();
boolean removeMetadata = request.isRemoveMetadata(); boolean removeMetadata = request.isRemoveMetadata();
boolean removeLinks = request.isRemoveLinks(); boolean removeLinks = request.isRemoveLinks();
boolean removeFonts = request.isRemoveFonts(); boolean removeFonts = request.isRemoveFonts();
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
if (removeJavaScript) { if (removeJavaScript) {
sanitizeJavaScript(document); sanitizeJavaScript(document);
} }
if (removeEmbeddedFiles) { if (removeEmbeddedFiles) {
sanitizeEmbeddedFiles(document); sanitizeEmbeddedFiles(document);
} }
if (removeMetadata) { if (removeMetadata) {
sanitizeMetadata(document); sanitizeMetadata(document);
} }
if (removeLinks) { if (removeLinks) {
sanitizeLinks(document); sanitizeLinks(document);
} }
if (removeFonts) { if (removeFonts) {
sanitizeFonts(document); sanitizeFonts(document);
} }
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
+ "_sanitized.pdf"); + "_sanitized.pdf");
} }
} }
private void sanitizeJavaScript(PDDocument document) throws IOException { private void sanitizeJavaScript(PDDocument document) throws IOException {
// Get the root dictionary (catalog) of the PDF // Get the root dictionary (catalog) of the PDF
PDDocumentCatalog catalog = document.getDocumentCatalog(); PDDocumentCatalog catalog = document.getDocumentCatalog();
// Get the Names dictionary // Get the Names dictionary
COSDictionary namesDict = COSDictionary namesDict =
(COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES); (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES);
if (namesDict != null) { if (namesDict != null) {
// Get the JavaScript dictionary // Get the JavaScript dictionary
COSDictionary javaScriptDict = COSDictionary javaScriptDict =
(COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript")); (COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript"));
if (javaScriptDict != null) { if (javaScriptDict != null) {
// Remove the JavaScript dictionary // Remove the JavaScript dictionary
namesDict.removeItem(COSName.getPDFName("JavaScript")); namesDict.removeItem(COSName.getPDFName("JavaScript"));
} }
} }
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {
for (PDAnnotation annotation : page.getAnnotations()) { for (PDAnnotation annotation : page.getAnnotations()) {
if (annotation instanceof PDAnnotationWidget) { if (annotation instanceof PDAnnotationWidget) {
PDAnnotationWidget widget = (PDAnnotationWidget) annotation; PDAnnotationWidget widget = (PDAnnotationWidget) annotation;
PDAction action = widget.getAction(); PDAction action = widget.getAction();
if (action instanceof PDActionJavaScript) { if (action instanceof PDActionJavaScript) {
widget.setAction(null); widget.setAction(null);
} }
} }
} }
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm != null) { if (acroForm != null) {
for (PDField field : acroForm.getFields()) { for (PDField field : acroForm.getFields()) {
PDFormFieldAdditionalActions actions = field.getActions(); PDFormFieldAdditionalActions actions = field.getActions();
if (actions != null) { if (actions != null) {
if (actions.getC() instanceof PDActionJavaScript) { if (actions.getC() instanceof PDActionJavaScript) {
actions.setC(null); actions.setC(null);
} }
if (actions.getF() instanceof PDActionJavaScript) { if (actions.getF() instanceof PDActionJavaScript) {
actions.setF(null); actions.setF(null);
} }
if (actions.getK() instanceof PDActionJavaScript) { if (actions.getK() instanceof PDActionJavaScript) {
actions.setK(null); actions.setK(null);
} }
if (actions.getV() instanceof PDActionJavaScript) { if (actions.getV() instanceof PDActionJavaScript) {
actions.setV(null); actions.setV(null);
} }
} }
} }
} }
} }
} }
private void sanitizeEmbeddedFiles(PDDocument document) { private void sanitizeEmbeddedFiles(PDDocument document) {
PDPageTree allPages = document.getPages(); PDPageTree allPages = document.getPages();
for (PDPage page : allPages) { for (PDPage page : allPages) {
PDResources res = page.getResources(); PDResources res = page.getResources();
// Remove embedded files from the PDF // Remove embedded files from the PDF
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles")); res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
} }
} }
private void sanitizeMetadata(PDDocument document) { private void sanitizeMetadata(PDDocument document) {
PDMetadata metadata = document.getDocumentCatalog().getMetadata(); PDMetadata metadata = document.getDocumentCatalog().getMetadata();
if (metadata != null) { if (metadata != null) {
document.getDocumentCatalog().setMetadata(null); document.getDocumentCatalog().setMetadata(null);
} }
} }
private void sanitizeLinks(PDDocument document) throws IOException { private void sanitizeLinks(PDDocument document) throws IOException {
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {
for (PDAnnotation annotation : page.getAnnotations()) { for (PDAnnotation annotation : page.getAnnotations()) {
if (annotation instanceof PDAnnotationLink) { if (annotation instanceof PDAnnotationLink) {
PDAction action = ((PDAnnotationLink) annotation).getAction(); PDAction action = ((PDAnnotationLink) annotation).getAction();
if (action instanceof PDActionLaunch || action instanceof PDActionURI) { if (action instanceof PDActionLaunch || action instanceof PDActionURI) {
((PDAnnotationLink) annotation).setAction(null); ((PDAnnotationLink) annotation).setAction(null);
} }
} }
} }
} }
} }
private void sanitizeFonts(PDDocument document) { private void sanitizeFonts(PDDocument document) {
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font")); page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
} }
} }
} }

View file

@ -1,113 +1,113 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@Controller @Controller
@Tag(name = "Convert", description = "Convert APIs") @Tag(name = "Convert", description = "Convert APIs")
public class ConverterWebController { public class ConverterWebController {
@GetMapping("/img-to-pdf") @GetMapping("/img-to-pdf")
@Hidden @Hidden
public String convertImgToPdfForm(Model model) { public String convertImgToPdfForm(Model model) {
model.addAttribute("currentPage", "img-to-pdf"); model.addAttribute("currentPage", "img-to-pdf");
return "convert/img-to-pdf"; return "convert/img-to-pdf";
} }
@GetMapping("/html-to-pdf") @GetMapping("/html-to-pdf")
@Hidden @Hidden
public String convertHTMLToPdfForm(Model model) { public String convertHTMLToPdfForm(Model model) {
model.addAttribute("currentPage", "html-to-pdf"); model.addAttribute("currentPage", "html-to-pdf");
return "convert/html-to-pdf"; return "convert/html-to-pdf";
} }
@GetMapping("/markdown-to-pdf") @GetMapping("/markdown-to-pdf")
@Hidden @Hidden
public String convertMarkdownToPdfForm(Model model) { public String convertMarkdownToPdfForm(Model model) {
model.addAttribute("currentPage", "markdown-to-pdf"); model.addAttribute("currentPage", "markdown-to-pdf");
return "convert/markdown-to-pdf"; return "convert/markdown-to-pdf";
} }
@GetMapping("/url-to-pdf") @GetMapping("/url-to-pdf")
@Hidden @Hidden
public String convertURLToPdfForm(Model model) { public String convertURLToPdfForm(Model model) {
model.addAttribute("currentPage", "url-to-pdf"); model.addAttribute("currentPage", "url-to-pdf");
return "convert/url-to-pdf"; return "convert/url-to-pdf";
} }
@GetMapping("/pdf-to-img") @GetMapping("/pdf-to-img")
@Hidden @Hidden
public String pdfToimgForm(Model model) { public String pdfToimgForm(Model model) {
model.addAttribute("currentPage", "pdf-to-img"); model.addAttribute("currentPage", "pdf-to-img");
return "convert/pdf-to-img"; return "convert/pdf-to-img";
} }
@GetMapping("/file-to-pdf") @GetMapping("/file-to-pdf")
@Hidden @Hidden
public String convertToPdfForm(Model model) { public String convertToPdfForm(Model model) {
model.addAttribute("currentPage", "file-to-pdf"); model.addAttribute("currentPage", "file-to-pdf");
return "convert/file-to-pdf"; return "convert/file-to-pdf";
} }
// PDF TO...... // PDF TO......
@GetMapping("/pdf-to-html") @GetMapping("/pdf-to-html")
@Hidden @Hidden
public ModelAndView pdfToHTML() { public ModelAndView pdfToHTML() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html");
modelAndView.addObject("currentPage", "pdf-to-html"); modelAndView.addObject("currentPage", "pdf-to-html");
return modelAndView; return modelAndView;
} }
@GetMapping("/pdf-to-presentation") @GetMapping("/pdf-to-presentation")
@Hidden @Hidden
public ModelAndView pdfToPresentation() { public ModelAndView pdfToPresentation() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation"); ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation");
modelAndView.addObject("currentPage", "pdf-to-presentation"); modelAndView.addObject("currentPage", "pdf-to-presentation");
return modelAndView; return modelAndView;
} }
@GetMapping("/pdf-to-text") @GetMapping("/pdf-to-text")
@Hidden @Hidden
public ModelAndView pdfToText() { public ModelAndView pdfToText() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text"); ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text");
modelAndView.addObject("currentPage", "pdf-to-text"); modelAndView.addObject("currentPage", "pdf-to-text");
return modelAndView; return modelAndView;
} }
@GetMapping("/pdf-to-word") @GetMapping("/pdf-to-word")
@Hidden @Hidden
public ModelAndView pdfToWord() { public ModelAndView pdfToWord() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word");
modelAndView.addObject("currentPage", "pdf-to-word"); modelAndView.addObject("currentPage", "pdf-to-word");
return modelAndView; return modelAndView;
} }
@GetMapping("/pdf-to-xml") @GetMapping("/pdf-to-xml")
@Hidden @Hidden
public ModelAndView pdfToXML() { public ModelAndView pdfToXML() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml"); ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml");
modelAndView.addObject("currentPage", "pdf-to-xml"); modelAndView.addObject("currentPage", "pdf-to-xml");
return modelAndView; return modelAndView;
} }
@GetMapping("/pdf-to-csv") @GetMapping("/pdf-to-csv")
@Hidden @Hidden
public ModelAndView pdfToCSV() { public ModelAndView pdfToCSV() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv"); ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv");
modelAndView.addObject("currentPage", "pdf-to-csv"); modelAndView.addObject("currentPage", "pdf-to-csv");
return modelAndView; return modelAndView;
} }
@GetMapping("/pdf-to-pdfa") @GetMapping("/pdf-to-pdfa")
@Hidden @Hidden
public String pdfToPdfAForm(Model model) { public String pdfToPdfAForm(Model model) {
model.addAttribute("currentPage", "pdf-to-pdfa"); model.addAttribute("currentPage", "pdf-to-pdfa");
return "convert/pdf-to-pdfa"; return "convert/pdf-to-pdfa";
} }
} }

View file

@ -1,69 +1,69 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@Controller @Controller
@Tag(name = "Security", description = "Security APIs") @Tag(name = "Security", description = "Security APIs")
public class SecurityWebController { public class SecurityWebController {
@GetMapping("/auto-redact") @GetMapping("/auto-redact")
@Hidden @Hidden
public String autoRedactForm(Model model) { public String autoRedactForm(Model model) {
model.addAttribute("currentPage", "auto-redact"); model.addAttribute("currentPage", "auto-redact");
return "security/auto-redact"; return "security/auto-redact";
} }
@GetMapping("/add-password") @GetMapping("/add-password")
@Hidden @Hidden
public String addPasswordForm(Model model) { public String addPasswordForm(Model model) {
model.addAttribute("currentPage", "add-password"); model.addAttribute("currentPage", "add-password");
return "security/add-password"; return "security/add-password";
} }
@GetMapping("/change-permissions") @GetMapping("/change-permissions")
@Hidden @Hidden
public String permissionsForm(Model model) { public String permissionsForm(Model model) {
model.addAttribute("currentPage", "change-permissions"); model.addAttribute("currentPage", "change-permissions");
return "security/change-permissions"; return "security/change-permissions";
} }
@GetMapping("/remove-password") @GetMapping("/remove-password")
@Hidden @Hidden
public String removePasswordForm(Model model) { public String removePasswordForm(Model model) {
model.addAttribute("currentPage", "remove-password"); model.addAttribute("currentPage", "remove-password");
return "security/remove-password"; return "security/remove-password";
} }
@GetMapping("/add-watermark") @GetMapping("/add-watermark")
@Hidden @Hidden
public String addWatermarkForm(Model model) { public String addWatermarkForm(Model model) {
model.addAttribute("currentPage", "add-watermark"); model.addAttribute("currentPage", "add-watermark");
return "security/add-watermark"; return "security/add-watermark";
} }
@GetMapping("/cert-sign") @GetMapping("/cert-sign")
@Hidden @Hidden
public String certSignForm(Model model) { public String certSignForm(Model model) {
model.addAttribute("currentPage", "cert-sign"); model.addAttribute("currentPage", "cert-sign");
return "security/cert-sign"; return "security/cert-sign";
} }
@GetMapping("/sanitize-pdf") @GetMapping("/sanitize-pdf")
@Hidden @Hidden
public String sanitizeForm(Model model) { public String sanitizeForm(Model model) {
model.addAttribute("currentPage", "sanitize-pdf"); model.addAttribute("currentPage", "sanitize-pdf");
return "security/sanitize-pdf"; return "security/sanitize-pdf";
} }
@GetMapping("/get-info-on-pdf") @GetMapping("/get-info-on-pdf")
@Hidden @Hidden
public String getInfo(Model model) { public String getInfo(Model model) {
model.addAttribute("currentPage", "get-info-on-pdf"); model.addAttribute("currentPage", "get-info-on-pdf");
return "security/get-info-on-pdf"; return "security/get-info-on-pdf";
} }
} }

View file

@ -1,186 +1,186 @@
package stirling.software.SPDF.utils; package stirling.software.SPDF.utils;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
public class GeneralUtils { public class GeneralUtils {
public static void deleteDirectory(Path path) throws IOException { public static void deleteDirectory(Path path) throws IOException {
Files.walkFileTree( Files.walkFileTree(
path, path,
new SimpleFileVisitor<Path>() { new SimpleFileVisitor<Path>() {
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException { throws IOException {
Files.delete(file); Files.delete(file);
return FileVisitResult.CONTINUE; return FileVisitResult.CONTINUE;
} }
@Override @Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException { throws IOException {
Files.delete(dir); Files.delete(dir);
return FileVisitResult.CONTINUE; return FileVisitResult.CONTINUE;
} }
}); });
} }
public static String convertToFileName(String name) { public static String convertToFileName(String name) {
String safeName = name.replaceAll("[^a-zA-Z0-9]", "_"); String safeName = name.replaceAll("[^a-zA-Z0-9]", "_");
if (safeName.length() > 50) { if (safeName.length() > 50) {
safeName = safeName.substring(0, 50); safeName = safeName.substring(0, 50);
} }
return safeName; return safeName;
} }
public static boolean isValidURL(String urlStr) { public static boolean isValidURL(String urlStr) {
try { try {
new URL(urlStr); new URL(urlStr);
return true; return true;
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
return false; return false;
} }
} }
public static File multipartToFile(MultipartFile multipart) throws IOException { public static File multipartToFile(MultipartFile multipart) throws IOException {
Path tempFile = Files.createTempFile("overlay-", ".pdf"); Path tempFile = Files.createTempFile("overlay-", ".pdf");
try (InputStream in = multipart.getInputStream(); try (InputStream in = multipart.getInputStream();
FileOutputStream out = new FileOutputStream(tempFile.toFile())) { FileOutputStream out = new FileOutputStream(tempFile.toFile())) {
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int bytesRead; int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) { while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead); out.write(buffer, 0, bytesRead);
} }
} }
return tempFile.toFile(); return tempFile.toFile();
} }
public static Long convertSizeToBytes(String sizeStr) { public static Long convertSizeToBytes(String sizeStr) {
if (sizeStr == null) { if (sizeStr == null) {
return null; return null;
} }
sizeStr = sizeStr.trim().toUpperCase(); sizeStr = sizeStr.trim().toUpperCase();
try { try {
if (sizeStr.endsWith("KB")) { if (sizeStr.endsWith("KB")) {
return (long) return (long)
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);
} else if (sizeStr.endsWith("MB")) { } else if (sizeStr.endsWith("MB")) {
return (long) return (long)
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
* 1024 * 1024
* 1024); * 1024);
} else if (sizeStr.endsWith("GB")) { } else if (sizeStr.endsWith("GB")) {
return (long) return (long)
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
* 1024 * 1024
* 1024 * 1024
* 1024); * 1024);
} else if (sizeStr.endsWith("B")) { } else if (sizeStr.endsWith("B")) {
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
} else { } else {
// Assume MB if no unit is specified // Assume MB if no unit is specified
return (long) (Double.parseDouble(sizeStr) * 1024 * 1024); return (long) (Double.parseDouble(sizeStr) * 1024 * 1024);
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// The numeric part of the input string cannot be parsed, handle this case // The numeric part of the input string cannot be parsed, handle this case
} }
return null; return null;
} }
public static List<Integer> parsePageString(String pageOrder, int totalPages) { public static List<Integer> parsePageString(String pageOrder, int totalPages) {
return parsePageList(pageOrder.split(","), totalPages); return parsePageList(pageOrder.split(","), totalPages);
} }
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) { public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) {
List<Integer> newPageOrder = new ArrayList<>(); List<Integer> newPageOrder = new ArrayList<>();
// loop through the page order array // loop through the page order array
for (String element : pageOrderArr) { for (String element : pageOrderArr) {
if (element.equalsIgnoreCase("all")) { if (element.equalsIgnoreCase("all")) {
for (int i = 0; i < totalPages; i++) { for (int i = 0; i < totalPages; i++) {
newPageOrder.add(i); newPageOrder.add(i);
} }
// As all pages are already added, no need to check further // As all pages are already added, no need to check further
break; break;
} else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { } else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) {
// Handle page order as a function // Handle page order as a function
int coefficient = 0; int coefficient = 0;
int constant = 0; int constant = 0;
boolean coefficientExists = false; boolean coefficientExists = false;
boolean constantExists = false; boolean constantExists = false;
if (element.contains("n")) { if (element.contains("n")) {
String[] parts = element.split("n"); String[] parts = element.split("n");
if (!parts[0].equals("") && parts[0] != null) { if (!parts[0].equals("") && parts[0] != null) {
coefficient = Integer.parseInt(parts[0]); coefficient = Integer.parseInt(parts[0]);
coefficientExists = true; coefficientExists = true;
} }
if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) {
constant = Integer.parseInt(parts[1]); constant = Integer.parseInt(parts[1]);
constantExists = true; constantExists = true;
} }
} else if (element.contains("+")) { } else if (element.contains("+")) {
constant = Integer.parseInt(element.replace("+", "")); constant = Integer.parseInt(element.replace("+", ""));
constantExists = true; constantExists = true;
} }
for (int i = 1; i <= totalPages; i++) { for (int i = 1; i <= totalPages; i++) {
int pageNum = coefficientExists ? coefficient * i : i; int pageNum = coefficientExists ? coefficient * i : i;
pageNum += constantExists ? constant : 0; pageNum += constantExists ? constant : 0;
if (pageNum <= totalPages && pageNum > 0) { if (pageNum <= totalPages && pageNum > 0) {
newPageOrder.add(pageNum - 1); newPageOrder.add(pageNum - 1);
} }
} }
} else if (element.contains("-")) { } else if (element.contains("-")) {
// split the range into start and end page // split the range into start and end page
String[] range = element.split("-"); String[] range = element.split("-");
int start = Integer.parseInt(range[0]); int start = Integer.parseInt(range[0]);
int end = Integer.parseInt(range[1]); int end = Integer.parseInt(range[1]);
// check if the end page is greater than total pages // check if the end page is greater than total pages
if (end > totalPages) { if (end > totalPages) {
end = totalPages; end = totalPages;
} }
// loop through the range of pages // loop through the range of pages
for (int j = start; j <= end; j++) { for (int j = start; j <= end; j++) {
// print the current index // print the current index
newPageOrder.add(j - 1); newPageOrder.add(j - 1);
} }
} else { } else {
// if the element is a single page // if the element is a single page
newPageOrder.add(Integer.parseInt(element) - 1); newPageOrder.add(Integer.parseInt(element) - 1);
} }
} }
return newPageOrder; return newPageOrder;
} }
public static boolean createDir(String path) { public static boolean createDir(String path) {
Path folder = Paths.get(path); Path folder = Paths.get(path);
if (!Files.exists(folder)) { if (!Files.exists(folder)) {
try { try {
Files.createDirectories(folder); Files.createDirectories(folder);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
return false; return false;
} }
} }
return true; return true;
} }
} }

View file

@ -1,400 +1,400 @@
package stirling.software.SPDF.utils; package stirling.software.SPDF.utils;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import javax.imageio.IIOImage; import javax.imageio.IIOImage;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageReader; import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter; import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.pdf.ImageFinder; import stirling.software.SPDF.pdf.ImageFinder;
public class PdfUtils { public class PdfUtils {
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
public static PDRectangle textToPageSize(String size) { public static PDRectangle textToPageSize(String size) {
switch (size.toUpperCase()) { switch (size.toUpperCase()) {
case "A0": case "A0":
return PDRectangle.A0; return PDRectangle.A0;
case "A1": case "A1":
return PDRectangle.A1; return PDRectangle.A1;
case "A2": case "A2":
return PDRectangle.A2; return PDRectangle.A2;
case "A3": case "A3":
return PDRectangle.A3; return PDRectangle.A3;
case "A4": case "A4":
return PDRectangle.A4; return PDRectangle.A4;
case "A5": case "A5":
return PDRectangle.A5; return PDRectangle.A5;
case "A6": case "A6":
return PDRectangle.A6; return PDRectangle.A6;
case "LETTER": case "LETTER":
return PDRectangle.LETTER; return PDRectangle.LETTER;
case "LEGAL": case "LEGAL":
return PDRectangle.LEGAL; return PDRectangle.LEGAL;
default: default:
throw new IllegalArgumentException("Invalid standard page size: " + size); throw new IllegalArgumentException("Invalid standard page size: " + size);
} }
} }
public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException { public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException {
String[] pageOrderArr = pagesToCheck.split(","); String[] pageOrderArr = pagesToCheck.split(",");
List<Integer> pageList = List<Integer> pageList =
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
for (int pageNumber : pageList) { for (int pageNumber : pageList) {
PDPage page = document.getPage(pageNumber); PDPage page = document.getPage(pageNumber);
if (hasImagesOnPage(page)) { if (hasImagesOnPage(page)) {
return true; return true;
} }
} }
return false; return false;
} }
public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase) public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase)
throws IOException { throws IOException {
String[] pageOrderArr = pageNumbersToCheck.split(","); String[] pageOrderArr = pageNumbersToCheck.split(",");
List<Integer> pageList = List<Integer> pageList =
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
for (int pageNumber : pageList) { for (int pageNumber : pageList) {
PDPage page = document.getPage(pageNumber); PDPage page = document.getPage(pageNumber);
if (hasTextOnPage(page, phrase)) { if (hasTextOnPage(page, phrase)) {
return true; return true;
} }
} }
return false; return false;
} }
public static boolean hasImagesOnPage(PDPage page) throws IOException { public static boolean hasImagesOnPage(PDPage page) throws IOException {
ImageFinder imageFinder = new ImageFinder(page); ImageFinder imageFinder = new ImageFinder(page);
imageFinder.processPage(page); imageFinder.processPage(page);
return imageFinder.hasImages(); return imageFinder.hasImages();
} }
public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException { public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException {
PDFTextStripper textStripper = new PDFTextStripper(); PDFTextStripper textStripper = new PDFTextStripper();
PDDocument tempDoc = new PDDocument(); PDDocument tempDoc = new PDDocument();
tempDoc.addPage(page); tempDoc.addPage(page);
String pageText = textStripper.getText(tempDoc); String pageText = textStripper.getText(tempDoc);
tempDoc.close(); tempDoc.close();
return pageText.contains(phrase); return pageText.contains(phrase);
} }
public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck)
throws IOException { throws IOException {
PDFTextStripper textStripper = new PDFTextStripper(); PDFTextStripper textStripper = new PDFTextStripper();
String pdfText = ""; String pdfText = "";
if (pagesToCheck == null || pagesToCheck.equals("all")) { if (pagesToCheck == null || pagesToCheck.equals("all")) {
pdfText = textStripper.getText(pdfDocument); pdfText = textStripper.getText(pdfDocument);
} else { } else {
// remove whitespaces // remove whitespaces
pagesToCheck = pagesToCheck.replaceAll("\\s+", ""); pagesToCheck = pagesToCheck.replaceAll("\\s+", "");
String[] splitPoints = pagesToCheck.split(","); String[] splitPoints = pagesToCheck.split(",");
for (String splitPoint : splitPoints) { for (String splitPoint : splitPoints) {
if (splitPoint.contains("-")) { if (splitPoint.contains("-")) {
// Handle page ranges // Handle page ranges
String[] range = splitPoint.split("-"); String[] range = splitPoint.split("-");
int startPage = Integer.parseInt(range[0]); int startPage = Integer.parseInt(range[0]);
int endPage = Integer.parseInt(range[1]); int endPage = Integer.parseInt(range[1]);
for (int i = startPage; i <= endPage; i++) { for (int i = startPage; i <= endPage; i++) {
textStripper.setStartPage(i); textStripper.setStartPage(i);
textStripper.setEndPage(i); textStripper.setEndPage(i);
pdfText += textStripper.getText(pdfDocument); pdfText += textStripper.getText(pdfDocument);
} }
} else { } else {
// Handle individual page // Handle individual page
int page = Integer.parseInt(splitPoint); int page = Integer.parseInt(splitPoint);
textStripper.setStartPage(page); textStripper.setStartPage(page);
textStripper.setEndPage(page); textStripper.setEndPage(page);
pdfText += textStripper.getText(pdfDocument); pdfText += textStripper.getText(pdfDocument);
} }
} }
} }
pdfDocument.close(); pdfDocument.close();
return pdfText.contains(text); return pdfText.contains(text);
} }
public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator)
throws IOException { throws IOException {
int actualPageCount = pdfDocument.getNumberOfPages(); int actualPageCount = pdfDocument.getNumberOfPages();
pdfDocument.close(); pdfDocument.close();
switch (comparator.toLowerCase()) { switch (comparator.toLowerCase()) {
case "greater": case "greater":
return actualPageCount > pageCount; return actualPageCount > pageCount;
case "equal": case "equal":
return actualPageCount == pageCount; return actualPageCount == pageCount;
case "less": case "less":
return actualPageCount < pageCount; return actualPageCount < pageCount;
default: default:
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Invalid comparator. Only 'greater', 'equal', and 'less' are supported."); "Invalid comparator. Only 'greater', 'equal', and 'less' are supported.");
} }
} }
public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException { public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException {
PDPage firstPage = pdfDocument.getPage(0); PDPage firstPage = pdfDocument.getPage(0);
PDRectangle mediaBox = firstPage.getMediaBox(); PDRectangle mediaBox = firstPage.getMediaBox();
float actualPageWidth = mediaBox.getWidth(); float actualPageWidth = mediaBox.getWidth();
float actualPageHeight = mediaBox.getHeight(); float actualPageHeight = mediaBox.getHeight();
pdfDocument.close(); pdfDocument.close();
// Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4 // Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4
String[] dimensions = expectedPageSize.split("x"); String[] dimensions = expectedPageSize.split("x");
float expectedPageWidth = Float.parseFloat(dimensions[0]); float expectedPageWidth = Float.parseFloat(dimensions[0]);
float expectedPageHeight = Float.parseFloat(dimensions[1]); float expectedPageHeight = Float.parseFloat(dimensions[1]);
// Checks if the actual page size matches the expected page size // Checks if the actual page size matches the expected page size
return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight; return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight;
} }
public static byte[] convertFromPdf( public static byte[] convertFromPdf(
byte[] inputStream, byte[] inputStream,
String imageType, String imageType,
ImageType colorType, ImageType colorType,
boolean singleImage, boolean singleImage,
int DPI, int DPI,
String filename) String filename)
throws IOException, Exception { throws IOException, Exception {
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
int pageCount = document.getNumberOfPages(); int pageCount = document.getNumberOfPages();
// Create a ByteArrayOutputStream to save the image(s) to // Create a ByteArrayOutputStream to save the image(s) to
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (singleImage) { if (singleImage) {
if (imageType.toLowerCase().equals("tiff") if (imageType.toLowerCase().equals("tiff")
|| imageType.toLowerCase().equals("tif")) { || imageType.toLowerCase().equals("tif")) {
// Write the images to the output stream as a TIFF with multiple frames // Write the images to the output stream as a TIFF with multiple frames
ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next(); ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next();
ImageWriteParam param = writer.getDefaultWriteParam(); ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionType("ZLib"); param.setCompressionType("ZLib");
param.setCompressionQuality(1.0f); param.setCompressionQuality(1.0f);
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) { try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {
writer.setOutput(ios); writer.setOutput(ios);
writer.prepareWriteSequence(null); writer.prepareWriteSequence(null);
for (int i = 0; i < pageCount; ++i) { for (int i = 0; i < pageCount; ++i) {
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
writer.writeToSequence(new IIOImage(image, null, null), param); writer.writeToSequence(new IIOImage(image, null, null), param);
} }
writer.endWriteSequence(); writer.endWriteSequence();
} }
writer.dispose(); writer.dispose();
} else { } else {
// Combine all images into a single big image // Combine all images into a single big image
BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType); BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType);
BufferedImage combined = BufferedImage combined =
new BufferedImage( new BufferedImage(
image.getWidth(), image.getWidth(),
image.getHeight() * pageCount, image.getHeight() * pageCount,
BufferedImage.TYPE_INT_RGB); BufferedImage.TYPE_INT_RGB);
Graphics g = combined.getGraphics(); Graphics g = combined.getGraphics();
for (int i = 0; i < pageCount; ++i) { for (int i = 0; i < pageCount; ++i) {
if (i != 0) { if (i != 0) {
image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
} }
g.drawImage(image, 0, i * image.getHeight(), null); g.drawImage(image, 0, i * image.getHeight(), null);
} }
// Write the image to the output stream // Write the image to the output stream
ImageIO.write(combined, imageType, baos); ImageIO.write(combined, imageType, baos);
} }
// Log that the image was successfully written to the byte array // Log that the image was successfully written to the byte array
logger.info("Image successfully written to byte array"); logger.info("Image successfully written to byte array");
} else { } else {
// Zip the images and return as byte array // Zip the images and return as byte array
try (ZipOutputStream zos = new ZipOutputStream(baos)) { try (ZipOutputStream zos = new ZipOutputStream(baos)) {
for (int i = 0; i < pageCount; ++i) { for (int i = 0; i < pageCount; ++i) {
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) { try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) {
ImageIO.write(image, imageType, baosImage); ImageIO.write(image, imageType, baosImage);
// Add the image to the zip file // Add the image to the zip file
zos.putNextEntry( zos.putNextEntry(
new ZipEntry( new ZipEntry(
String.format( String.format(
filename + "_%d.%s", filename + "_%d.%s",
i + 1, i + 1,
imageType.toLowerCase()))); imageType.toLowerCase())));
zos.write(baosImage.toByteArray()); zos.write(baosImage.toByteArray());
} }
} }
// Log that the images were successfully written to the byte array // Log that the images were successfully written to the byte array
logger.info("Images successfully written to byte array as a zip"); logger.info("Images successfully written to byte array as a zip");
} }
} }
return baos.toByteArray(); return baos.toByteArray();
} catch (IOException e) { } catch (IOException e) {
// Log an error message if there is an issue converting the PDF to an image // Log an error message if there is an issue converting the PDF to an image
logger.error("Error converting PDF to image", e); logger.error("Error converting PDF to image", e);
throw e; throw e;
} }
} }
public static byte[] imageToPdf( public static byte[] imageToPdf(
MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) MultipartFile[] files, String fitOption, boolean autoRotate, String colorType)
throws IOException { throws IOException {
try (PDDocument doc = new PDDocument()) { try (PDDocument doc = new PDDocument()) {
for (MultipartFile file : files) { for (MultipartFile file : files) {
String contentType = file.getContentType(); String contentType = file.getContentType();
String originalFilename = file.getOriginalFilename(); String originalFilename = file.getOriginalFilename();
if (originalFilename != null if (originalFilename != null
&& (originalFilename.toLowerCase().endsWith(".tiff") && (originalFilename.toLowerCase().endsWith(".tiff")
|| originalFilename.toLowerCase().endsWith(".tif"))) { || originalFilename.toLowerCase().endsWith(".tif"))) {
ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next(); ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next();
reader.setInput(ImageIO.createImageInputStream(file.getInputStream())); reader.setInput(ImageIO.createImageInputStream(file.getInputStream()));
int numPages = reader.getNumImages(true); int numPages = reader.getNumImages(true);
for (int i = 0; i < numPages; i++) { for (int i = 0; i < numPages; i++) {
BufferedImage pageImage = reader.read(i); BufferedImage pageImage = reader.read(i);
BufferedImage convertedImage = BufferedImage convertedImage =
ImageProcessingUtils.convertColorType(pageImage, colorType); ImageProcessingUtils.convertColorType(pageImage, colorType);
PDImageXObject pdImage = PDImageXObject pdImage =
LosslessFactory.createFromImage(doc, convertedImage); LosslessFactory.createFromImage(doc, convertedImage);
addImageToDocument(doc, pdImage, fitOption, autoRotate); addImageToDocument(doc, pdImage, fitOption, autoRotate);
} }
} else { } else {
BufferedImage image = ImageIO.read(file.getInputStream()); BufferedImage image = ImageIO.read(file.getInputStream());
BufferedImage convertedImage = BufferedImage convertedImage =
ImageProcessingUtils.convertColorType(image, colorType); ImageProcessingUtils.convertColorType(image, colorType);
// Use JPEGFactory if it's JPEG since JPEG is lossy // Use JPEGFactory if it's JPEG since JPEG is lossy
PDImageXObject pdImage = PDImageXObject pdImage =
(contentType != null && contentType.equals("image/jpeg")) (contentType != null && contentType.equals("image/jpeg"))
? JPEGFactory.createFromImage(doc, convertedImage) ? JPEGFactory.createFromImage(doc, convertedImage)
: LosslessFactory.createFromImage(doc, convertedImage); : LosslessFactory.createFromImage(doc, convertedImage);
addImageToDocument(doc, pdImage, fitOption, autoRotate); addImageToDocument(doc, pdImage, fitOption, autoRotate);
} }
} }
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
doc.save(byteArrayOutputStream); doc.save(byteArrayOutputStream);
logger.info("PDF successfully saved to byte array"); logger.info("PDF successfully saved to byte array");
return byteArrayOutputStream.toByteArray(); return byteArrayOutputStream.toByteArray();
} }
} }
private static void addImageToDocument( private static void addImageToDocument(
PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate) PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate)
throws IOException { throws IOException {
boolean imageIsLandscape = image.getWidth() > image.getHeight(); boolean imageIsLandscape = image.getWidth() > image.getHeight();
PDRectangle pageSize = PDRectangle.A4; PDRectangle pageSize = PDRectangle.A4;
System.out.println(fitOption); System.out.println(fitOption);
if (autoRotate && imageIsLandscape) { if (autoRotate && imageIsLandscape) {
pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth()); pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth());
} }
if ("fitDocumentToImage".equals(fitOption)) { if ("fitDocumentToImage".equals(fitOption)) {
pageSize = new PDRectangle(image.getWidth(), image.getHeight()); pageSize = new PDRectangle(image.getWidth(), image.getHeight());
} }
PDPage page = new PDPage(pageSize); PDPage page = new PDPage(pageSize);
doc.addPage(page); doc.addPage(page);
float pageWidth = page.getMediaBox().getWidth(); float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight(); float pageHeight = page.getMediaBox().getHeight();
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) { try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
if ("fillPage".equals(fitOption) || "fitDocumentToImage".equals(fitOption)) { if ("fillPage".equals(fitOption) || "fitDocumentToImage".equals(fitOption)) {
contentStream.drawImage(image, 0, 0, pageWidth, pageHeight); contentStream.drawImage(image, 0, 0, pageWidth, pageHeight);
} else if ("maintainAspectRatio".equals(fitOption)) { } else if ("maintainAspectRatio".equals(fitOption)) {
float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight(); float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight();
float pageAspectRatio = pageWidth / pageHeight; float pageAspectRatio = pageWidth / pageHeight;
float scaleFactor = 1.0f; float scaleFactor = 1.0f;
if (imageAspectRatio > pageAspectRatio) { if (imageAspectRatio > pageAspectRatio) {
scaleFactor = pageWidth / image.getWidth(); scaleFactor = pageWidth / image.getWidth();
} else { } else {
scaleFactor = pageHeight / image.getHeight(); scaleFactor = pageHeight / image.getHeight();
} }
float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2; float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2;
float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2; float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2;
contentStream.drawImage( contentStream.drawImage(
image, image,
xPos, xPos,
yPos, yPos,
image.getWidth() * scaleFactor, image.getWidth() * scaleFactor,
image.getHeight() * scaleFactor); image.getHeight() * scaleFactor);
} }
} catch (IOException e) { } catch (IOException e) {
logger.error("Error adding image to PDF", e); logger.error("Error adding image to PDF", e);
throw e; throw e;
} }
} }
public static byte[] overlayImage( public static byte[] overlayImage(
byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage)
throws IOException { throws IOException {
PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes)); PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes));
// Get the first page of the PDF // Get the first page of the PDF
int pages = document.getNumberOfPages(); int pages = document.getNumberOfPages();
for (int i = 0; i < pages; i++) { for (int i = 0; i < pages; i++) {
PDPage page = document.getPage(i); PDPage page = document.getPage(i);
try (PDPageContentStream contentStream = try (PDPageContentStream contentStream =
new PDPageContentStream( new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true)) { document, page, PDPageContentStream.AppendMode.APPEND, true)) {
// Create an image object from the image bytes // Create an image object from the image bytes
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
// Draw the image onto the page at the specified x and y coordinates // Draw the image onto the page at the specified x and y coordinates
contentStream.drawImage(image, x, y); contentStream.drawImage(image, x, y);
logger.info("Image successfully overlayed onto PDF"); logger.info("Image successfully overlayed onto PDF");
if (!everyPage && i == 0) { if (!everyPage && i == 0) {
break; break;
} }
} catch (IOException e) { } catch (IOException e) {
// Log an error message if there is an issue overlaying the image onto the PDF // Log an error message if there is an issue overlaying the image onto the PDF
logger.error("Error overlaying image onto PDF", e); logger.error("Error overlaying image onto PDF", e);
throw e; throw e;
} }
} }
// Create a ByteArrayOutputStream to save the PDF to // Create a ByteArrayOutputStream to save the PDF to
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos); document.save(baos);
logger.info("PDF successfully saved to byte array"); logger.info("PDF successfully saved to byte array");
return baos.toByteArray(); return baos.toByteArray();
} }
} }

View file

@ -1,67 +1,67 @@
package stirling.software.SPDF.utils; package stirling.software.SPDF.utils;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
public class WebResponseUtils { public class WebResponseUtils {
public static ResponseEntity<byte[]> boasToWebResponse( public static ResponseEntity<byte[]> boasToWebResponse(
ByteArrayOutputStream baos, String docName) throws IOException { ByteArrayOutputStream baos, String docName) throws IOException {
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName); return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName);
} }
public static ResponseEntity<byte[]> boasToWebResponse( public static ResponseEntity<byte[]> boasToWebResponse(
ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException { ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException {
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType); return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType);
} }
public static ResponseEntity<byte[]> multiPartFileToWebResponse(MultipartFile file) public static ResponseEntity<byte[]> multiPartFileToWebResponse(MultipartFile file)
throws IOException { throws IOException {
String fileName = file.getOriginalFilename(); String fileName = file.getOriginalFilename();
MediaType mediaType = MediaType.parseMediaType(file.getContentType()); MediaType mediaType = MediaType.parseMediaType(file.getContentType());
byte[] bytes = file.getBytes(); byte[] bytes = file.getBytes();
return bytesToWebResponse(bytes, fileName, mediaType); return bytesToWebResponse(bytes, fileName, mediaType);
} }
public static ResponseEntity<byte[]> bytesToWebResponse( public static ResponseEntity<byte[]> bytesToWebResponse(
byte[] bytes, String docName, MediaType mediaType) throws IOException { byte[] bytes, String docName, MediaType mediaType) throws IOException {
// Return the PDF as a response // Return the PDF as a response
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(mediaType); headers.setContentType(mediaType);
headers.setContentLength(bytes.length); headers.setContentLength(bytes.length);
String encodedDocName = String encodedDocName =
URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()) URLEncoder.encode(docName, StandardCharsets.UTF_8.toString())
.replaceAll("\\+", "%20"); .replaceAll("\\+", "%20");
headers.setContentDispositionFormData("attachment", encodedDocName); headers.setContentDispositionFormData("attachment", encodedDocName);
return new ResponseEntity<>(bytes, headers, HttpStatus.OK); return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
} }
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName)
throws IOException { throws IOException {
return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF); return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF);
} }
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName)
throws IOException { throws IOException {
// Open Byte Array and save document to it // Open Byte Array and save document to it
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.save(baos); document.save(baos);
// Close the document // Close the document
document.close(); document.close();
return boasToWebResponse(baos, docName); return boasToWebResponse(baos, docName);
} }
} }