pull/644/head
parent
de9e9a0f84
commit
8a57165547
29 changed files with 3006 additions and 3007 deletions
@ -1,81 +1,81 @@ |
||||
package stirling.software.SPDF; |
||||
|
||||
import java.nio.file.Files; |
||||
import java.nio.file.Paths; |
||||
import java.util.Collections; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
import org.springframework.core.env.Environment; |
||||
import org.springframework.scheduling.annotation.EnableScheduling; |
||||
|
||||
import jakarta.annotation.PostConstruct; |
||||
import stirling.software.SPDF.config.ConfigInitializer; |
||||
import stirling.software.SPDF.utils.GeneralUtils; |
||||
|
||||
@SpringBootApplication |
||||
@EnableScheduling |
||||
public class SPdfApplication { |
||||
|
||||
@Autowired private Environment env; |
||||
|
||||
@PostConstruct |
||||
public void init() { |
||||
// Check if the BROWSER_OPEN environment variable is set to true
|
||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN"); |
||||
boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true"); |
||||
|
||||
if (browserOpen) { |
||||
try { |
||||
String url = "http://localhost:" + getPort(); |
||||
|
||||
String os = System.getProperty("os.name").toLowerCase(); |
||||
Runtime rt = Runtime.getRuntime(); |
||||
if (os.contains("win")) { |
||||
// For Windows
|
||||
rt.exec("rundll32 url.dll,FileProtocolHandler " + url); |
||||
} |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
SpringApplication app = new SpringApplication(SPdfApplication.class); |
||||
app.addInitializers(new ConfigInitializer()); |
||||
if (Files.exists(Paths.get("configs/settings.yml"))) { |
||||
app.setDefaultProperties( |
||||
Collections.singletonMap( |
||||
"spring.config.additional-location", "file:configs/settings.yml")); |
||||
} else { |
||||
System.out.println( |
||||
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); |
||||
} |
||||
app.run(args); |
||||
|
||||
try { |
||||
Thread.sleep(1000); |
||||
} catch (InterruptedException e) { |
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace(); |
||||
} |
||||
|
||||
GeneralUtils.createDir("customFiles/static/"); |
||||
GeneralUtils.createDir("customFiles/templates/"); |
||||
|
||||
System.out.println("Stirling-PDF Started."); |
||||
|
||||
String url = "http://localhost:" + getPort(); |
||||
System.out.println("Navigate to " + url); |
||||
} |
||||
|
||||
public static String getPort() { |
||||
String port = System.getProperty("local.server.port"); |
||||
if (port == null || port.isEmpty()) { |
||||
port = "8080"; |
||||
} |
||||
return port; |
||||
} |
||||
} |
||||
package stirling.software.SPDF; |
||||
|
||||
import java.nio.file.Files; |
||||
import java.nio.file.Paths; |
||||
import java.util.Collections; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
import org.springframework.core.env.Environment; |
||||
import org.springframework.scheduling.annotation.EnableScheduling; |
||||
|
||||
import jakarta.annotation.PostConstruct; |
||||
import stirling.software.SPDF.config.ConfigInitializer; |
||||
import stirling.software.SPDF.utils.GeneralUtils; |
||||
|
||||
@SpringBootApplication |
||||
@EnableScheduling |
||||
public class SPdfApplication { |
||||
|
||||
@Autowired private Environment env; |
||||
|
||||
@PostConstruct |
||||
public void init() { |
||||
// Check if the BROWSER_OPEN environment variable is set to true
|
||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN"); |
||||
boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true"); |
||||
|
||||
if (browserOpen) { |
||||
try { |
||||
String url = "http://localhost:" + getPort(); |
||||
|
||||
String os = System.getProperty("os.name").toLowerCase(); |
||||
Runtime rt = Runtime.getRuntime(); |
||||
if (os.contains("win")) { |
||||
// For Windows
|
||||
rt.exec("rundll32 url.dll,FileProtocolHandler " + url); |
||||
} |
||||
} catch (Exception e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
SpringApplication app = new SpringApplication(SPdfApplication.class); |
||||
app.addInitializers(new ConfigInitializer()); |
||||
if (Files.exists(Paths.get("configs/settings.yml"))) { |
||||
app.setDefaultProperties( |
||||
Collections.singletonMap( |
||||
"spring.config.additional-location", "file:configs/settings.yml")); |
||||
} else { |
||||
System.out.println( |
||||
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); |
||||
} |
||||
app.run(args); |
||||
|
||||
try { |
||||
Thread.sleep(1000); |
||||
} catch (InterruptedException e) { |
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace(); |
||||
} |
||||
|
||||
GeneralUtils.createDir("customFiles/static/"); |
||||
GeneralUtils.createDir("customFiles/templates/"); |
||||
|
||||
System.out.println("Stirling-PDF Started."); |
||||
|
||||
String url = "http://localhost:" + getPort(); |
||||
System.out.println("Navigate to " + url); |
||||
} |
||||
|
||||
public static String getPort() { |
||||
String port = System.getProperty("local.server.port"); |
||||
if (port == null || port.isEmpty()) { |
||||
port = "8080"; |
||||
} |
||||
return port; |
||||
} |
||||
} |
||||
|
@ -1,60 +1,60 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
|
||||
@Configuration |
||||
public class AppConfig { |
||||
|
||||
@Autowired ApplicationProperties applicationProperties; |
||||
|
||||
@Bean(name = "loginEnabled") |
||||
public boolean loginEnabled() { |
||||
return applicationProperties.getSecurity().getEnableLogin(); |
||||
} |
||||
|
||||
@Bean(name = "appName") |
||||
public String appName() { |
||||
String homeTitle = applicationProperties.getUi().getAppName(); |
||||
return (homeTitle != null) ? homeTitle : "Stirling PDF"; |
||||
} |
||||
|
||||
@Bean(name = "appVersion") |
||||
public String appVersion() { |
||||
String version = getClass().getPackage().getImplementationVersion(); |
||||
return (version != null) ? version : "0.0.0"; |
||||
} |
||||
|
||||
@Bean(name = "homeText") |
||||
public String homeText() { |
||||
return (applicationProperties.getUi().getHomeDescription() != null) |
||||
? applicationProperties.getUi().getHomeDescription() |
||||
: "null"; |
||||
} |
||||
|
||||
@Bean(name = "navBarText") |
||||
public String navBarText() { |
||||
String defaultNavBar = |
||||
applicationProperties.getUi().getAppNameNavbar() != null |
||||
? applicationProperties.getUi().getAppNameNavbar() |
||||
: applicationProperties.getUi().getAppName(); |
||||
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF"; |
||||
} |
||||
|
||||
@Bean(name = "enableAlphaFunctionality") |
||||
public boolean enableAlphaFunctionality() { |
||||
return applicationProperties.getSystem().getEnableAlphaFunctionality() != null |
||||
? applicationProperties.getSystem().getEnableAlphaFunctionality() |
||||
: false; |
||||
} |
||||
|
||||
@Bean(name = "rateLimit") |
||||
public boolean rateLimit() { |
||||
String appName = System.getProperty("rateLimit"); |
||||
if (appName == null) appName = System.getenv("rateLimit"); |
||||
return (appName != null) ? Boolean.valueOf(appName) : false; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
|
||||
@Configuration |
||||
public class AppConfig { |
||||
|
||||
@Autowired ApplicationProperties applicationProperties; |
||||
|
||||
@Bean(name = "loginEnabled") |
||||
public boolean loginEnabled() { |
||||
return applicationProperties.getSecurity().getEnableLogin(); |
||||
} |
||||
|
||||
@Bean(name = "appName") |
||||
public String appName() { |
||||
String homeTitle = applicationProperties.getUi().getAppName(); |
||||
return (homeTitle != null) ? homeTitle : "Stirling PDF"; |
||||
} |
||||
|
||||
@Bean(name = "appVersion") |
||||
public String appVersion() { |
||||
String version = getClass().getPackage().getImplementationVersion(); |
||||
return (version != null) ? version : "0.0.0"; |
||||
} |
||||
|
||||
@Bean(name = "homeText") |
||||
public String homeText() { |
||||
return (applicationProperties.getUi().getHomeDescription() != null) |
||||
? applicationProperties.getUi().getHomeDescription() |
||||
: "null"; |
||||
} |
||||
|
||||
@Bean(name = "navBarText") |
||||
public String navBarText() { |
||||
String defaultNavBar = |
||||
applicationProperties.getUi().getAppNameNavbar() != null |
||||
? applicationProperties.getUi().getAppNameNavbar() |
||||
: applicationProperties.getUi().getAppName(); |
||||
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF"; |
||||
} |
||||
|
||||
@Bean(name = "enableAlphaFunctionality") |
||||
public boolean enableAlphaFunctionality() { |
||||
return applicationProperties.getSystem().getEnableAlphaFunctionality() != null |
||||
? applicationProperties.getSystem().getEnableAlphaFunctionality() |
||||
: false; |
||||
} |
||||
|
||||
@Bean(name = "rateLimit") |
||||
public boolean rateLimit() { |
||||
String appName = System.getProperty("rateLimit"); |
||||
if (appName == null) appName = System.getenv("rateLimit"); |
||||
return (appName != null) ? Boolean.valueOf(appName) : false; |
||||
} |
||||
} |
||||
|
@ -1,64 +1,64 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.util.Locale; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.web.servlet.LocaleResolver; |
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; |
||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
|
||||
@Configuration |
||||
public class Beans implements WebMvcConfigurer { |
||||
|
||||
@Autowired ApplicationProperties applicationProperties; |
||||
|
||||
@Override |
||||
public void addInterceptors(InterceptorRegistry registry) { |
||||
registry.addInterceptor(localeChangeInterceptor()); |
||||
registry.addInterceptor(new CleanUrlInterceptor()); |
||||
} |
||||
|
||||
@Bean |
||||
public LocaleChangeInterceptor localeChangeInterceptor() { |
||||
LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); |
||||
lci.setParamName("lang"); |
||||
return lci; |
||||
} |
||||
|
||||
@Bean |
||||
public LocaleResolver localeResolver() { |
||||
SessionLocaleResolver slr = new SessionLocaleResolver(); |
||||
|
||||
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale(); |
||||
Locale defaultLocale = |
||||
Locale.UK; // Fallback to UK locale if environment variable is not set
|
||||
|
||||
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) { |
||||
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv); |
||||
String tempLanguageTag = tempLocale.toLanguageTag(); |
||||
|
||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { |
||||
defaultLocale = tempLocale; |
||||
} else { |
||||
tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-")); |
||||
tempLanguageTag = tempLocale.toLanguageTag(); |
||||
|
||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { |
||||
defaultLocale = tempLocale; |
||||
} else { |
||||
System.err.println( |
||||
"Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK."); |
||||
} |
||||
} |
||||
} |
||||
|
||||
slr.setDefaultLocale(defaultLocale); |
||||
return slr; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.util.Locale; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.web.servlet.LocaleResolver; |
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; |
||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
|
||||
@Configuration |
||||
public class Beans implements WebMvcConfigurer { |
||||
|
||||
@Autowired ApplicationProperties applicationProperties; |
||||
|
||||
@Override |
||||
public void addInterceptors(InterceptorRegistry registry) { |
||||
registry.addInterceptor(localeChangeInterceptor()); |
||||
registry.addInterceptor(new CleanUrlInterceptor()); |
||||
} |
||||
|
||||
@Bean |
||||
public LocaleChangeInterceptor localeChangeInterceptor() { |
||||
LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); |
||||
lci.setParamName("lang"); |
||||
return lci; |
||||
} |
||||
|
||||
@Bean |
||||
public LocaleResolver localeResolver() { |
||||
SessionLocaleResolver slr = new SessionLocaleResolver(); |
||||
|
||||
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale(); |
||||
Locale defaultLocale = |
||||
Locale.UK; // Fallback to UK locale if environment variable is not set
|
||||
|
||||
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) { |
||||
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv); |
||||
String tempLanguageTag = tempLocale.toLanguageTag(); |
||||
|
||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { |
||||
defaultLocale = tempLocale; |
||||
} else { |
||||
tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-")); |
||||
tempLanguageTag = tempLocale.toLanguageTag(); |
||||
|
||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) { |
||||
defaultLocale = tempLocale; |
||||
} else { |
||||
System.err.println( |
||||
"Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK."); |
||||
} |
||||
} |
||||
} |
||||
|
||||
slr.setDefaultLocale(defaultLocale); |
||||
return slr; |
||||
} |
||||
} |
||||
|
@ -1,74 +1,74 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.web.servlet.HandlerInterceptor; |
||||
import org.springframework.web.servlet.ModelAndView; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
|
||||
public class CleanUrlInterceptor implements HandlerInterceptor { |
||||
|
||||
private static final List<String> ALLOWED_PARAMS = |
||||
Arrays.asList( |
||||
"lang", "endpoint", "endpoints", "logout", "error", "file", "messageType"); |
||||
|
||||
@Override |
||||
public boolean preHandle( |
||||
HttpServletRequest request, HttpServletResponse response, Object handler) |
||||
throws Exception { |
||||
String queryString = request.getQueryString(); |
||||
if (queryString != null && !queryString.isEmpty()) { |
||||
String requestURI = request.getRequestURI(); |
||||
Map<String, String> parameters = new HashMap<>(); |
||||
|
||||
// Keep only the allowed parameters
|
||||
String[] queryParameters = queryString.split("&"); |
||||
for (String param : queryParameters) { |
||||
String[] keyValue = param.split("="); |
||||
if (keyValue.length != 2) { |
||||
continue; |
||||
} |
||||
if (ALLOWED_PARAMS.contains(keyValue[0])) { |
||||
parameters.put(keyValue[0], keyValue[1]); |
||||
} |
||||
} |
||||
|
||||
// If there are any parameters that are not allowed
|
||||
if (parameters.size() != queryParameters.length) { |
||||
// Construct new query string
|
||||
StringBuilder newQueryString = new StringBuilder(); |
||||
for (Map.Entry<String, String> entry : parameters.entrySet()) { |
||||
if (newQueryString.length() > 0) { |
||||
newQueryString.append("&"); |
||||
} |
||||
newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); |
||||
} |
||||
|
||||
// Redirect to the URL with only allowed query parameters
|
||||
String redirectUrl = requestURI + "?" + newQueryString; |
||||
response.sendRedirect(redirectUrl); |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public void postHandle( |
||||
HttpServletRequest request, |
||||
HttpServletResponse response, |
||||
Object handler, |
||||
ModelAndView modelAndView) {} |
||||
|
||||
@Override |
||||
public void afterCompletion( |
||||
HttpServletRequest request, |
||||
HttpServletResponse response, |
||||
Object handler, |
||||
Exception ex) {} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.springframework.web.servlet.HandlerInterceptor; |
||||
import org.springframework.web.servlet.ModelAndView; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
|
||||
public class CleanUrlInterceptor implements HandlerInterceptor { |
||||
|
||||
private static final List<String> ALLOWED_PARAMS = |
||||
Arrays.asList( |
||||
"lang", "endpoint", "endpoints", "logout", "error", "file", "messageType"); |
||||
|
||||
@Override |
||||
public boolean preHandle( |
||||
HttpServletRequest request, HttpServletResponse response, Object handler) |
||||
throws Exception { |
||||
String queryString = request.getQueryString(); |
||||
if (queryString != null && !queryString.isEmpty()) { |
||||
String requestURI = request.getRequestURI(); |
||||
Map<String, String> parameters = new HashMap<>(); |
||||
|
||||
// Keep only the allowed parameters
|
||||
String[] queryParameters = queryString.split("&"); |
||||
for (String param : queryParameters) { |
||||
String[] keyValue = param.split("="); |
||||
if (keyValue.length != 2) { |
||||
continue; |
||||
} |
||||
if (ALLOWED_PARAMS.contains(keyValue[0])) { |
||||
parameters.put(keyValue[0], keyValue[1]); |
||||
} |
||||
} |
||||
|
||||
// If there are any parameters that are not allowed
|
||||
if (parameters.size() != queryParameters.length) { |
||||
// Construct new query string
|
||||
StringBuilder newQueryString = new StringBuilder(); |
||||
for (Map.Entry<String, String> entry : parameters.entrySet()) { |
||||
if (newQueryString.length() > 0) { |
||||
newQueryString.append("&"); |
||||
} |
||||
newQueryString.append(entry.getKey()).append("=").append(entry.getValue()); |
||||
} |
||||
|
||||
// Redirect to the URL with only allowed query parameters
|
||||
String redirectUrl = requestURI + "?" + newQueryString; |
||||
response.sendRedirect(redirectUrl); |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public void postHandle( |
||||
HttpServletRequest request, |
||||
HttpServletResponse response, |
||||
Object handler, |
||||
ModelAndView modelAndView) {} |
||||
|
||||
@Override |
||||
public void afterCompletion( |
||||
HttpServletRequest request, |
||||
HttpServletResponse response, |
||||
Object handler, |
||||
Exception ex) {} |
||||
} |
||||
|
@ -1,231 +1,231 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
|
||||
@Service |
||||
public class EndpointConfiguration { |
||||
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); |
||||
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>(); |
||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>(); |
||||
|
||||
private final ApplicationProperties applicationProperties; |
||||
|
||||
@Autowired |
||||
public EndpointConfiguration(ApplicationProperties applicationProperties) { |
||||
this.applicationProperties = applicationProperties; |
||||
init(); |
||||
processEnvironmentConfigs(); |
||||
} |
||||
|
||||
public void enableEndpoint(String endpoint) { |
||||
endpointStatuses.put(endpoint, true); |
||||
} |
||||
|
||||
public void disableEndpoint(String endpoint) { |
||||
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { |
||||
logger.info("Disabling {}", endpoint); |
||||
endpointStatuses.put(endpoint, false); |
||||
} |
||||
} |
||||
|
||||
public boolean isEndpointEnabled(String endpoint) { |
||||
if (endpoint.startsWith("/")) { |
||||
endpoint = endpoint.substring(1); |
||||
} |
||||
return endpointStatuses.getOrDefault(endpoint, true); |
||||
} |
||||
|
||||
public void addEndpointToGroup(String group, String endpoint) { |
||||
endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint); |
||||
} |
||||
|
||||
public void enableGroup(String group) { |
||||
Set<String> endpoints = endpointGroups.get(group); |
||||
if (endpoints != null) { |
||||
for (String endpoint : endpoints) { |
||||
enableEndpoint(endpoint); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void disableGroup(String group) { |
||||
Set<String> endpoints = endpointGroups.get(group); |
||||
if (endpoints != null) { |
||||
for (String endpoint : endpoints) { |
||||
disableEndpoint(endpoint); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void init() { |
||||
// Adding endpoints to "PageOps" group
|
||||
addEndpointToGroup("PageOps", "remove-pages"); |
||||
addEndpointToGroup("PageOps", "merge-pdfs"); |
||||
addEndpointToGroup("PageOps", "split-pdfs"); |
||||
addEndpointToGroup("PageOps", "pdf-organizer"); |
||||
addEndpointToGroup("PageOps", "rotate-pdf"); |
||||
addEndpointToGroup("PageOps", "multi-page-layout"); |
||||
addEndpointToGroup("PageOps", "scale-pages"); |
||||
addEndpointToGroup("PageOps", "adjust-contrast"); |
||||
addEndpointToGroup("PageOps", "crop"); |
||||
addEndpointToGroup("PageOps", "auto-split-pdf"); |
||||
addEndpointToGroup("PageOps", "extract-page"); |
||||
addEndpointToGroup("PageOps", "pdf-to-single-page"); |
||||
addEndpointToGroup("PageOps", "split-by-size-or-count"); |
||||
addEndpointToGroup("PageOps", "overlay-pdf"); |
||||
addEndpointToGroup("PageOps", "split-pdf-by-sections"); |
||||
|
||||
// Adding endpoints to "Convert" group
|
||||
addEndpointToGroup("Convert", "pdf-to-img"); |
||||
addEndpointToGroup("Convert", "img-to-pdf"); |
||||
addEndpointToGroup("Convert", "pdf-to-pdfa"); |
||||
addEndpointToGroup("Convert", "file-to-pdf"); |
||||
addEndpointToGroup("Convert", "xlsx-to-pdf"); |
||||
addEndpointToGroup("Convert", "pdf-to-word"); |
||||
addEndpointToGroup("Convert", "pdf-to-presentation"); |
||||
addEndpointToGroup("Convert", "pdf-to-text"); |
||||
addEndpointToGroup("Convert", "pdf-to-html"); |
||||
addEndpointToGroup("Convert", "pdf-to-xml"); |
||||
addEndpointToGroup("Convert", "html-to-pdf"); |
||||
addEndpointToGroup("Convert", "url-to-pdf"); |
||||
addEndpointToGroup("Convert", "markdown-to-pdf"); |
||||
addEndpointToGroup("Convert", "pdf-to-csv"); |
||||
|
||||
// Adding endpoints to "Security" group
|
||||
addEndpointToGroup("Security", "add-password"); |
||||
addEndpointToGroup("Security", "remove-password"); |
||||
addEndpointToGroup("Security", "change-permissions"); |
||||
addEndpointToGroup("Security", "add-watermark"); |
||||
addEndpointToGroup("Security", "cert-sign"); |
||||
addEndpointToGroup("Security", "sanitize-pdf"); |
||||
addEndpointToGroup("Security", "auto-redact"); |
||||
|
||||
// Adding endpoints to "Other" group
|
||||
addEndpointToGroup("Other", "ocr-pdf"); |
||||
addEndpointToGroup("Other", "add-image"); |
||||
addEndpointToGroup("Other", "compress-pdf"); |
||||
addEndpointToGroup("Other", "extract-images"); |
||||
addEndpointToGroup("Other", "change-metadata"); |
||||
addEndpointToGroup("Other", "extract-image-scans"); |
||||
addEndpointToGroup("Other", "sign"); |
||||
addEndpointToGroup("Other", "flatten"); |
||||
addEndpointToGroup("Other", "repair"); |
||||
addEndpointToGroup("Other", "remove-blanks"); |
||||
addEndpointToGroup("Other", "remove-annotations"); |
||||
addEndpointToGroup("Other", "compare"); |
||||
addEndpointToGroup("Other", "add-page-numbers"); |
||||
addEndpointToGroup("Other", "auto-rename"); |
||||
addEndpointToGroup("Other", "get-info-on-pdf"); |
||||
addEndpointToGroup("Other", "show-javascript"); |
||||
|
||||
// CLI
|
||||
addEndpointToGroup("CLI", "compress-pdf"); |
||||
addEndpointToGroup("CLI", "extract-image-scans"); |
||||
addEndpointToGroup("CLI", "remove-blanks"); |
||||
addEndpointToGroup("CLI", "repair"); |
||||
addEndpointToGroup("CLI", "pdf-to-pdfa"); |
||||
addEndpointToGroup("CLI", "file-to-pdf"); |
||||
addEndpointToGroup("CLI", "xlsx-to-pdf"); |
||||
addEndpointToGroup("CLI", "pdf-to-word"); |
||||
addEndpointToGroup("CLI", "pdf-to-presentation"); |
||||
addEndpointToGroup("CLI", "pdf-to-text"); |
||||
addEndpointToGroup("CLI", "pdf-to-html"); |
||||
addEndpointToGroup("CLI", "pdf-to-xml"); |
||||
addEndpointToGroup("CLI", "ocr-pdf"); |
||||
addEndpointToGroup("CLI", "html-to-pdf"); |
||||
addEndpointToGroup("CLI", "url-to-pdf"); |
||||
|
||||
// python
|
||||
addEndpointToGroup("Python", "extract-image-scans"); |
||||
addEndpointToGroup("Python", "remove-blanks"); |
||||
addEndpointToGroup("Python", "html-to-pdf"); |
||||
addEndpointToGroup("Python", "url-to-pdf"); |
||||
|
||||
// openCV
|
||||
addEndpointToGroup("OpenCV", "extract-image-scans"); |
||||
addEndpointToGroup("OpenCV", "remove-blanks"); |
||||
|
||||
// LibreOffice
|
||||
addEndpointToGroup("LibreOffice", "repair"); |
||||
addEndpointToGroup("LibreOffice", "file-to-pdf"); |
||||
addEndpointToGroup("LibreOffice", "xlsx-to-pdf"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-word"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-presentation"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-text"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-html"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-xml"); |
||||
|
||||
// OCRmyPDF
|
||||
addEndpointToGroup("OCRmyPDF", "compress-pdf"); |
||||
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa"); |
||||
addEndpointToGroup("OCRmyPDF", "ocr-pdf"); |
||||
|
||||
// Java
|
||||
addEndpointToGroup("Java", "merge-pdfs"); |
||||
addEndpointToGroup("Java", "remove-pages"); |
||||
addEndpointToGroup("Java", "split-pdfs"); |
||||
addEndpointToGroup("Java", "pdf-organizer"); |
||||
addEndpointToGroup("Java", "rotate-pdf"); |
||||
addEndpointToGroup("Java", "pdf-to-img"); |
||||
addEndpointToGroup("Java", "img-to-pdf"); |
||||
addEndpointToGroup("Java", "add-password"); |
||||
addEndpointToGroup("Java", "remove-password"); |
||||
addEndpointToGroup("Java", "change-permissions"); |
||||
addEndpointToGroup("Java", "add-watermark"); |
||||
addEndpointToGroup("Java", "add-image"); |
||||
addEndpointToGroup("Java", "extract-images"); |
||||
addEndpointToGroup("Java", "change-metadata"); |
||||
addEndpointToGroup("Java", "cert-sign"); |
||||
addEndpointToGroup("Java", "multi-page-layout"); |
||||
addEndpointToGroup("Java", "scale-pages"); |
||||
addEndpointToGroup("Java", "add-page-numbers"); |
||||
addEndpointToGroup("Java", "auto-rename"); |
||||
addEndpointToGroup("Java", "auto-split-pdf"); |
||||
addEndpointToGroup("Java", "sanitize-pdf"); |
||||
addEndpointToGroup("Java", "crop"); |
||||
addEndpointToGroup("Java", "get-info-on-pdf"); |
||||
addEndpointToGroup("Java", "extract-page"); |
||||
addEndpointToGroup("Java", "pdf-to-single-page"); |
||||
addEndpointToGroup("Java", "markdown-to-pdf"); |
||||
addEndpointToGroup("Java", "show-javascript"); |
||||
addEndpointToGroup("Java", "auto-redact"); |
||||
addEndpointToGroup("Java", "pdf-to-csv"); |
||||
addEndpointToGroup("Java", "split-by-size-or-count"); |
||||
addEndpointToGroup("Java", "overlay-pdf"); |
||||
addEndpointToGroup("Java", "split-pdf-by-sections"); |
||||
|
||||
// Javascript
|
||||
addEndpointToGroup("Javascript", "pdf-organizer"); |
||||
addEndpointToGroup("Javascript", "sign"); |
||||
addEndpointToGroup("Javascript", "compare"); |
||||
addEndpointToGroup("Javascript", "adjust-contrast"); |
||||
} |
||||
|
||||
private void processEnvironmentConfigs() { |
||||
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); |
||||
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); |
||||
|
||||
if (endpointsToRemove != null) { |
||||
for (String endpoint : endpointsToRemove) { |
||||
disableEndpoint(endpoint.trim()); |
||||
} |
||||
} |
||||
|
||||
if (groupsToRemove != null) { |
||||
for (String group : groupsToRemove) { |
||||
disableGroup(group.trim()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
|
||||
@Service |
||||
public class EndpointConfiguration { |
||||
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); |
||||
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>(); |
||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>(); |
||||
|
||||
private final ApplicationProperties applicationProperties; |
||||
|
||||
@Autowired |
||||
public EndpointConfiguration(ApplicationProperties applicationProperties) { |
||||
this.applicationProperties = applicationProperties; |
||||
init(); |
||||
processEnvironmentConfigs(); |
||||
} |
||||
|
||||
public void enableEndpoint(String endpoint) { |
||||
endpointStatuses.put(endpoint, true); |
||||
} |
||||
|
||||
public void disableEndpoint(String endpoint) { |
||||
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { |
||||
logger.info("Disabling {}", endpoint); |
||||
endpointStatuses.put(endpoint, false); |
||||
} |
||||
} |
||||
|
||||
public boolean isEndpointEnabled(String endpoint) { |
||||
if (endpoint.startsWith("/")) { |
||||
endpoint = endpoint.substring(1); |
||||
} |
||||
return endpointStatuses.getOrDefault(endpoint, true); |
||||
} |
||||
|
||||
public void addEndpointToGroup(String group, String endpoint) { |
||||
endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint); |
||||
} |
||||
|
||||
public void enableGroup(String group) { |
||||
Set<String> endpoints = endpointGroups.get(group); |
||||
if (endpoints != null) { |
||||
for (String endpoint : endpoints) { |
||||
enableEndpoint(endpoint); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void disableGroup(String group) { |
||||
Set<String> endpoints = endpointGroups.get(group); |
||||
if (endpoints != null) { |
||||
for (String endpoint : endpoints) { |
||||
disableEndpoint(endpoint); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void init() { |
||||
// Adding endpoints to "PageOps" group
|
||||
addEndpointToGroup("PageOps", "remove-pages"); |
||||
addEndpointToGroup("PageOps", "merge-pdfs"); |
||||
addEndpointToGroup("PageOps", "split-pdfs"); |
||||
addEndpointToGroup("PageOps", "pdf-organizer"); |
||||
addEndpointToGroup("PageOps", "rotate-pdf"); |
||||
addEndpointToGroup("PageOps", "multi-page-layout"); |
||||
addEndpointToGroup("PageOps", "scale-pages"); |
||||
addEndpointToGroup("PageOps", "adjust-contrast"); |
||||
addEndpointToGroup("PageOps", "crop"); |
||||
addEndpointToGroup("PageOps", "auto-split-pdf"); |
||||
addEndpointToGroup("PageOps", "extract-page"); |
||||
addEndpointToGroup("PageOps", "pdf-to-single-page"); |
||||
addEndpointToGroup("PageOps", "split-by-size-or-count"); |
||||
addEndpointToGroup("PageOps", "overlay-pdf"); |
||||
addEndpointToGroup("PageOps", "split-pdf-by-sections"); |
||||
|
||||
// Adding endpoints to "Convert" group
|
||||
addEndpointToGroup("Convert", "pdf-to-img"); |
||||
addEndpointToGroup("Convert", "img-to-pdf"); |
||||
addEndpointToGroup("Convert", "pdf-to-pdfa"); |
||||
addEndpointToGroup("Convert", "file-to-pdf"); |
||||
addEndpointToGroup("Convert", "xlsx-to-pdf"); |
||||
addEndpointToGroup("Convert", "pdf-to-word"); |
||||
addEndpointToGroup("Convert", "pdf-to-presentation"); |
||||
addEndpointToGroup("Convert", "pdf-to-text"); |
||||
addEndpointToGroup("Convert", "pdf-to-html"); |
||||
addEndpointToGroup("Convert", "pdf-to-xml"); |
||||
addEndpointToGroup("Convert", "html-to-pdf"); |
||||
addEndpointToGroup("Convert", "url-to-pdf"); |
||||
addEndpointToGroup("Convert", "markdown-to-pdf"); |
||||
addEndpointToGroup("Convert", "pdf-to-csv"); |
||||
|
||||
// Adding endpoints to "Security" group
|
||||
addEndpointToGroup("Security", "add-password"); |
||||
addEndpointToGroup("Security", "remove-password"); |
||||
addEndpointToGroup("Security", "change-permissions"); |
||||
addEndpointToGroup("Security", "add-watermark"); |
||||
addEndpointToGroup("Security", "cert-sign"); |
||||
addEndpointToGroup("Security", "sanitize-pdf"); |
||||
addEndpointToGroup("Security", "auto-redact"); |
||||
|
||||
// Adding endpoints to "Other" group
|
||||
addEndpointToGroup("Other", "ocr-pdf"); |
||||
addEndpointToGroup("Other", "add-image"); |
||||
addEndpointToGroup("Other", "compress-pdf"); |
||||
addEndpointToGroup("Other", "extract-images"); |
||||
addEndpointToGroup("Other", "change-metadata"); |
||||
addEndpointToGroup("Other", "extract-image-scans"); |
||||
addEndpointToGroup("Other", "sign"); |
||||
addEndpointToGroup("Other", "flatten"); |
||||
addEndpointToGroup("Other", "repair"); |
||||
addEndpointToGroup("Other", "remove-blanks"); |
||||
addEndpointToGroup("Other", "remove-annotations"); |
||||
addEndpointToGroup("Other", "compare"); |
||||
addEndpointToGroup("Other", "add-page-numbers"); |
||||
addEndpointToGroup("Other", "auto-rename"); |
||||
addEndpointToGroup("Other", "get-info-on-pdf"); |
||||
addEndpointToGroup("Other", "show-javascript"); |
||||
|
||||
// CLI
|
||||
addEndpointToGroup("CLI", "compress-pdf"); |
||||
addEndpointToGroup("CLI", "extract-image-scans"); |
||||
addEndpointToGroup("CLI", "remove-blanks"); |
||||
addEndpointToGroup("CLI", "repair"); |
||||
addEndpointToGroup("CLI", "pdf-to-pdfa"); |
||||
addEndpointToGroup("CLI", "file-to-pdf"); |
||||
addEndpointToGroup("CLI", "xlsx-to-pdf"); |
||||
addEndpointToGroup("CLI", "pdf-to-word"); |
||||
addEndpointToGroup("CLI", "pdf-to-presentation"); |
||||
addEndpointToGroup("CLI", "pdf-to-text"); |
||||
addEndpointToGroup("CLI", "pdf-to-html"); |
||||
addEndpointToGroup("CLI", "pdf-to-xml"); |
||||
addEndpointToGroup("CLI", "ocr-pdf"); |
||||
addEndpointToGroup("CLI", "html-to-pdf"); |
||||
addEndpointToGroup("CLI", "url-to-pdf"); |
||||
|
||||
// python
|
||||
addEndpointToGroup("Python", "extract-image-scans"); |
||||
addEndpointToGroup("Python", "remove-blanks"); |
||||
addEndpointToGroup("Python", "html-to-pdf"); |
||||
addEndpointToGroup("Python", "url-to-pdf"); |
||||
|
||||
// openCV
|
||||
addEndpointToGroup("OpenCV", "extract-image-scans"); |
||||
addEndpointToGroup("OpenCV", "remove-blanks"); |
||||
|
||||
// LibreOffice
|
||||
addEndpointToGroup("LibreOffice", "repair"); |
||||
addEndpointToGroup("LibreOffice", "file-to-pdf"); |
||||
addEndpointToGroup("LibreOffice", "xlsx-to-pdf"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-word"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-presentation"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-text"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-html"); |
||||
addEndpointToGroup("LibreOffice", "pdf-to-xml"); |
||||
|
||||
// OCRmyPDF
|
||||
addEndpointToGroup("OCRmyPDF", "compress-pdf"); |
||||
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa"); |
||||
addEndpointToGroup("OCRmyPDF", "ocr-pdf"); |
||||
|
||||
// Java
|
||||
addEndpointToGroup("Java", "merge-pdfs"); |
||||
addEndpointToGroup("Java", "remove-pages"); |
||||
addEndpointToGroup("Java", "split-pdfs"); |
||||
addEndpointToGroup("Java", "pdf-organizer"); |
||||
addEndpointToGroup("Java", "rotate-pdf"); |
||||
addEndpointToGroup("Java", "pdf-to-img"); |
||||
addEndpointToGroup("Java", "img-to-pdf"); |
||||
addEndpointToGroup("Java", "add-password"); |
||||
addEndpointToGroup("Java", "remove-password"); |
||||
addEndpointToGroup("Java", "change-permissions"); |
||||
addEndpointToGroup("Java", "add-watermark"); |
||||
addEndpointToGroup("Java", "add-image"); |
||||
addEndpointToGroup("Java", "extract-images"); |
||||
addEndpointToGroup("Java", "change-metadata"); |
||||
addEndpointToGroup("Java", "cert-sign"); |
||||
addEndpointToGroup("Java", "multi-page-layout"); |
||||
addEndpointToGroup("Java", "scale-pages"); |
||||
addEndpointToGroup("Java", "add-page-numbers"); |
||||
addEndpointToGroup("Java", "auto-rename"); |
||||
addEndpointToGroup("Java", "auto-split-pdf"); |
||||
addEndpointToGroup("Java", "sanitize-pdf"); |
||||
addEndpointToGroup("Java", "crop"); |
||||
addEndpointToGroup("Java", "get-info-on-pdf"); |
||||
addEndpointToGroup("Java", "extract-page"); |
||||
addEndpointToGroup("Java", "pdf-to-single-page"); |
||||
addEndpointToGroup("Java", "markdown-to-pdf"); |
||||
addEndpointToGroup("Java", "show-javascript"); |
||||
addEndpointToGroup("Java", "auto-redact"); |
||||
addEndpointToGroup("Java", "pdf-to-csv"); |
||||
addEndpointToGroup("Java", "split-by-size-or-count"); |
||||
addEndpointToGroup("Java", "overlay-pdf"); |
||||
addEndpointToGroup("Java", "split-pdf-by-sections"); |
||||
|
||||
// Javascript
|
||||
addEndpointToGroup("Javascript", "pdf-organizer"); |
||||
addEndpointToGroup("Javascript", "sign"); |
||||
addEndpointToGroup("Javascript", "compare"); |
||||
addEndpointToGroup("Javascript", "adjust-contrast"); |
||||
} |
||||
|
||||
private void processEnvironmentConfigs() { |
||||
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); |
||||
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); |
||||
|
||||
if (endpointsToRemove != null) { |
||||
for (String endpoint : endpointsToRemove) { |
||||
disableEndpoint(endpoint.trim()); |
||||
} |
||||
} |
||||
|
||||
if (groupsToRemove != null) { |
||||
for (String group : groupsToRemove) { |
||||
disableGroup(group.trim()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,26 +1,26 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.servlet.HandlerInterceptor; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
|
||||
@Component |
||||
public class EndpointInterceptor implements HandlerInterceptor { |
||||
|
||||
@Autowired private EndpointConfiguration endpointConfiguration; |
||||
|
||||
@Override |
||||
public boolean preHandle( |
||||
HttpServletRequest request, HttpServletResponse response, Object handler) |
||||
throws Exception { |
||||
String requestURI = request.getRequestURI(); |
||||
if (!endpointConfiguration.isEndpointEnabled(requestURI)) { |
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled"); |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.servlet.HandlerInterceptor; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
|
||||
@Component |
||||
public class EndpointInterceptor implements HandlerInterceptor { |
||||
|
||||
@Autowired private EndpointConfiguration endpointConfiguration; |
||||
|
||||
@Override |
||||
public boolean preHandle( |
||||
HttpServletRequest request, HttpServletResponse response, Object handler) |
||||
throws Exception { |
||||
String requestURI = request.getRequestURI(); |
||||
if (!endpointConfiguration.isEndpointEnabled(requestURI)) { |
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled"); |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
} |
||||
|
@ -1,25 +1,25 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import io.micrometer.core.instrument.Meter; |
||||
import io.micrometer.core.instrument.config.MeterFilter; |
||||
import io.micrometer.core.instrument.config.MeterFilterReply; |
||||
|
||||
@Configuration |
||||
public class MetricsConfig { |
||||
|
||||
@Bean |
||||
public MeterFilter meterFilter() { |
||||
return new MeterFilter() { |
||||
@Override |
||||
public MeterFilterReply accept(Meter.Id id) { |
||||
if (id.getName().equals("http.requests")) { |
||||
return MeterFilterReply.NEUTRAL; |
||||
} |
||||
return MeterFilterReply.DENY; |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import io.micrometer.core.instrument.Meter; |
||||
import io.micrometer.core.instrument.config.MeterFilter; |
||||
import io.micrometer.core.instrument.config.MeterFilterReply; |
||||
|
||||
@Configuration |
||||
public class MetricsConfig { |
||||
|
||||
@Bean |
||||
public MeterFilter meterFilter() { |
||||
return new MeterFilter() { |
||||
@Override |
||||
public MeterFilterReply accept(Meter.Id id) { |
||||
if (id.getName().equals("http.requests")) { |
||||
return MeterFilterReply.NEUTRAL; |
||||
} |
||||
return MeterFilterReply.DENY; |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
|
@ -1,64 +1,64 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
|
||||
import io.micrometer.core.instrument.Counter; |
||||
import io.micrometer.core.instrument.MeterRegistry; |
||||
|
||||
import jakarta.servlet.FilterChain; |
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
|
||||
@Component |
||||
public class MetricsFilter extends OncePerRequestFilter { |
||||
|
||||
private final MeterRegistry meterRegistry; |
||||
|
||||
@Autowired |
||||
public MetricsFilter(MeterRegistry meterRegistry) { |
||||
this.meterRegistry = meterRegistry; |
||||
} |
||||
|
||||
@Override |
||||
protected void doFilterInternal( |
||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
String uri = request.getRequestURI(); |
||||
|
||||
// System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
||||
// Ignore static resources
|
||||
if (!(uri.startsWith("/js") |
||||
|| uri.startsWith("/v1/api-docs") |
||||
|| uri.endsWith("robots.txt") |
||||
|| uri.startsWith("/images") |
||||
|| uri.startsWith("/images") |
||||
|| uri.endsWith(".png") |
||||
|| uri.endsWith(".ico") |
||||
|| uri.endsWith(".css") |
||||
|| uri.endsWith(".map") |
||||
|| uri.endsWith(".svg") |
||||
|| uri.endsWith(".js") |
||||
|| uri.contains("swagger") |
||||
|| uri.startsWith("/api/v1/info") |
||||
|| uri.startsWith("/site.webmanifest") |
||||
|| uri.startsWith("/fonts") |
||||
|| uri.startsWith("/pdfjs"))) { |
||||
|
||||
Counter counter = |
||||
Counter.builder("http.requests") |
||||
.tag("uri", uri) |
||||
.tag("method", request.getMethod()) |
||||
.register(meterRegistry); |
||||
|
||||
counter.increment(); |
||||
// System.out.println("Counted");
|
||||
} |
||||
|
||||
filterChain.doFilter(request, response); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
|
||||
import io.micrometer.core.instrument.Counter; |
||||
import io.micrometer.core.instrument.MeterRegistry; |
||||
|
||||
import jakarta.servlet.FilterChain; |
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
|
||||
@Component |
||||
public class MetricsFilter extends OncePerRequestFilter { |
||||
|
||||
private final MeterRegistry meterRegistry; |
||||
|
||||
@Autowired |
||||
public MetricsFilter(MeterRegistry meterRegistry) { |
||||
this.meterRegistry = meterRegistry; |
||||
} |
||||
|
||||
@Override |
||||
protected void doFilterInternal( |
||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
String uri = request.getRequestURI(); |
||||
|
||||
// System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
||||
// Ignore static resources
|
||||
if (!(uri.startsWith("/js") |
||||
|| uri.startsWith("/v1/api-docs") |
||||
|| uri.endsWith("robots.txt") |
||||
|| uri.startsWith("/images") |
||||
|| uri.startsWith("/images") |
||||
|| uri.endsWith(".png") |
||||
|| uri.endsWith(".ico") |
||||
|| uri.endsWith(".css") |
||||
|| uri.endsWith(".map") |
||||
|| uri.endsWith(".svg") |
||||
|| uri.endsWith(".js") |
||||
|| uri.contains("swagger") |
||||
|| uri.startsWith("/api/v1/info") |
||||
|| uri.startsWith("/site.webmanifest") |
||||
|| uri.startsWith("/fonts") |
||||
|| uri.startsWith("/pdfjs"))) { |
||||
|
||||
Counter counter = |
||||
Counter.builder("http.requests") |
||||
.tag("uri", uri) |
||||
.tag("method", request.getMethod()) |
||||
.register(meterRegistry); |
||||
|
||||
counter.increment(); |
||||
// System.out.println("Counted");
|
||||
} |
||||
|
||||
filterChain.doFilter(request, response); |
||||
} |
||||
} |
||||
|
@ -1,53 +1,53 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import io.swagger.v3.oas.models.Components; |
||||
import io.swagger.v3.oas.models.OpenAPI; |
||||
import io.swagger.v3.oas.models.info.Info; |
||||
import io.swagger.v3.oas.models.security.SecurityRequirement; |
||||
import io.swagger.v3.oas.models.security.SecurityScheme; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
|
||||
@Configuration |
||||
public class OpenApiConfig { |
||||
|
||||
@Autowired ApplicationProperties applicationProperties; |
||||
|
||||
@Bean |
||||
public OpenAPI customOpenAPI() { |
||||
String version = getClass().getPackage().getImplementationVersion(); |
||||
if (version == null) { |
||||
version = "1.0.0"; // default version if all else fails
|
||||
} |
||||
|
||||
SecurityScheme apiKeyScheme = |
||||
new SecurityScheme() |
||||
.type(SecurityScheme.Type.APIKEY) |
||||
.in(SecurityScheme.In.HEADER) |
||||
.name("X-API-KEY"); |
||||
if (!applicationProperties.getSecurity().getEnableLogin()) { |
||||
return new OpenAPI() |
||||
.components(new Components()) |
||||
.info( |
||||
new Info() |
||||
.title("Stirling PDF API") |
||||
.version(version) |
||||
.description( |
||||
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); |
||||
} else { |
||||
return new OpenAPI() |
||||
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) |
||||
.info( |
||||
new Info() |
||||
.title("Stirling PDF API") |
||||
.version(version) |
||||
.description( |
||||
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")) |
||||
.addSecurityItem(new SecurityRequirement().addList("apiKey")); |
||||
} |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
|
||||
import io.swagger.v3.oas.models.Components; |
||||
import io.swagger.v3.oas.models.OpenAPI; |
||||
import io.swagger.v3.oas.models.info.Info; |
||||
import io.swagger.v3.oas.models.security.SecurityRequirement; |
||||
import io.swagger.v3.oas.models.security.SecurityScheme; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
|
||||
@Configuration |
||||
public class OpenApiConfig { |
||||
|
||||
@Autowired ApplicationProperties applicationProperties; |
||||
|
||||
@Bean |
||||
public OpenAPI customOpenAPI() { |
||||
String version = getClass().getPackage().getImplementationVersion(); |
||||
if (version == null) { |
||||
version = "1.0.0"; // default version if all else fails
|
||||
} |
||||
|
||||
SecurityScheme apiKeyScheme = |
||||
new SecurityScheme() |
||||
.type(SecurityScheme.Type.APIKEY) |
||||
.in(SecurityScheme.In.HEADER) |
||||
.name("X-API-KEY"); |
||||
if (!applicationProperties.getSecurity().getEnableLogin()) { |
||||
return new OpenAPI() |
||||
.components(new Components()) |
||||
.info( |
||||
new Info() |
||||
.title("Stirling PDF API") |
||||
.version(version) |
||||
.description( |
||||
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")); |
||||
} else { |
||||
return new OpenAPI() |
||||
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) |
||||
.info( |
||||
new Info() |
||||
.title("Stirling PDF API") |
||||
.version(version) |
||||
.description( |
||||
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.")) |
||||
.addSecurityItem(new SecurityRequirement().addList("apiKey")); |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,18 +1,18 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.time.LocalDateTime; |
||||
|
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.context.event.ContextRefreshedEvent; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
@Component |
||||
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> { |
||||
|
||||
public static LocalDateTime startTime; |
||||
|
||||
@Override |
||||
public void onApplicationEvent(ContextRefreshedEvent event) { |
||||
startTime = LocalDateTime.now(); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import java.time.LocalDateTime; |
||||
|
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.context.event.ContextRefreshedEvent; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
@Component |
||||
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> { |
||||
|
||||
public static LocalDateTime startTime; |
||||
|
||||
@Override |
||||
public void onApplicationEvent(ContextRefreshedEvent event) { |
||||
startTime = LocalDateTime.now(); |
||||
} |
||||
} |
||||
|
@ -1,26 +1,26 @@ |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; |
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
||||
|
||||
@Configuration |
||||
public class WebMvcConfig implements WebMvcConfigurer { |
||||
|
||||
@Autowired private EndpointInterceptor endpointInterceptor; |
||||
|
||||
@Override |
||||
public void addInterceptors(InterceptorRegistry registry) { |
||||
registry.addInterceptor(endpointInterceptor); |
||||
} |
||||
|
||||
@Override |
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) { |
||||
// Handler for external static resources
|
||||
registry.addResourceHandler("/**") |
||||
.addResourceLocations("file:customFiles/static/", "classpath:/static/"); |
||||
// .setCachePeriod(0); // Optional: disable caching
|
||||
} |
||||
} |
||||
package stirling.software.SPDF.config; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; |
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
||||
|
||||
@Configuration |
||||
public class WebMvcConfig implements WebMvcConfigurer { |
||||
|
||||
@Autowired private EndpointInterceptor endpointInterceptor; |
||||
|
||||
@Override |
||||
public void addInterceptors(InterceptorRegistry registry) { |
||||
registry.addInterceptor(endpointInterceptor); |
||||
} |
||||
|
||||
@Override |
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) { |
||||
// Handler for external static resources
|
||||
registry.addResourceHandler("/**") |
||||
.addResourceLocations("file:customFiles/static/", "classpath:/static/"); |
||||
// .setCachePeriod(0); // Optional: disable caching
|
||||
} |
||||
} |
||||
|
@ -1,49 +1,49 @@ |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.authentication.BadCredentialsException; |
||||
import org.springframework.security.authentication.LockedException; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
|
||||
@Component |
||||
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { |
||||
|
||||
@Autowired private final LoginAttemptService loginAttemptService; |
||||
|
||||
@Autowired |
||||
public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) { |
||||
this.loginAttemptService = loginAttemptService; |
||||
} |
||||
|
||||
@Override |
||||
public void onAuthenticationFailure( |
||||
HttpServletRequest request, |
||||
HttpServletResponse response, |
||||
AuthenticationException exception) |
||||
throws IOException, ServletException { |
||||
String ip = request.getRemoteAddr(); |
||||
logger.error("Failed login attempt from IP: " + ip); |
||||
|
||||
String username = request.getParameter("username"); |
||||
if (loginAttemptService.loginAttemptCheck(username)) { |
||||
setDefaultFailureUrl("/login?error=locked"); |
||||
|
||||
} else { |
||||
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) { |
||||
setDefaultFailureUrl("/login?error=badcredentials"); |
||||
} else if (exception.getClass().isAssignableFrom(LockedException.class)) { |
||||
setDefaultFailureUrl("/login?error=locked"); |
||||
} |
||||
} |
||||
|
||||
super.onAuthenticationFailure(request, response, exception); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.authentication.BadCredentialsException; |
||||
import org.springframework.security.authentication.LockedException; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
|
||||
@Component |
||||
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { |
||||
|
||||
@Autowired private final LoginAttemptService loginAttemptService; |
||||
|
||||
@Autowired |
||||
public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) { |
||||
this.loginAttemptService = loginAttemptService; |
||||
} |
||||
|
||||
@Override |
||||
public void onAuthenticationFailure( |
||||
HttpServletRequest request, |
||||
HttpServletResponse response, |
||||
AuthenticationException exception) |
||||
throws IOException, ServletException { |
||||
String ip = request.getRemoteAddr(); |
||||
logger.error("Failed login attempt from IP: " + ip); |
||||
|
||||
String username = request.getParameter("username"); |
||||
if (loginAttemptService.loginAttemptCheck(username)) { |
||||
setDefaultFailureUrl("/login?error=locked"); |
||||
|
||||
} else { |
||||
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) { |
||||
setDefaultFailureUrl("/login?error=badcredentials"); |
||||
} else if (exception.getClass().isAssignableFrom(LockedException.class)) { |
||||
setDefaultFailureUrl("/login?error=locked"); |
||||
} |
||||
} |
||||
|
||||
super.onAuthenticationFailure(request, response, exception); |
||||
} |
||||
} |
||||
|
@ -1,57 +1,57 @@ |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Set; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.authentication.LockedException; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import stirling.software.SPDF.model.Authority; |
||||
import stirling.software.SPDF.model.User; |
||||
import stirling.software.SPDF.repository.UserRepository; |
||||
|
||||
@Service |
||||
public class CustomUserDetailsService implements UserDetailsService { |
||||
|
||||
@Autowired private UserRepository userRepository; |
||||
|
||||
@Autowired private LoginAttemptService loginAttemptService; |
||||
|
||||
@Override |
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
||||
User user = |
||||
userRepository |
||||
.findByUsername(username) |
||||
.orElseThrow( |
||||
() -> |
||||
new UsernameNotFoundException( |
||||
"No user found with username: " + username)); |
||||
|
||||
if (loginAttemptService.isBlocked(username)) { |
||||
throw new LockedException( |
||||
"Your account has been locked due to too many failed login attempts."); |
||||
} |
||||
|
||||
return new org.springframework.security.core.userdetails.User( |
||||
user.getUsername(), |
||||
user.getPassword(), |
||||
user.isEnabled(), |
||||
true, |
||||
true, |
||||
true, |
||||
getAuthorities(user.getAuthorities())); |
||||
} |
||||
|
||||
private Collection<? extends GrantedAuthority> getAuthorities(Set<Authority> authorities) { |
||||
return authorities.stream() |
||||
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Set; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.authentication.LockedException; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import stirling.software.SPDF.model.Authority; |
||||
import stirling.software.SPDF.model.User; |
||||
import stirling.software.SPDF.repository.UserRepository; |
||||
|
||||
@Service |
||||
public class CustomUserDetailsService implements UserDetailsService { |
||||
|
||||
@Autowired private UserRepository userRepository; |
||||
|
||||
@Autowired private LoginAttemptService loginAttemptService; |
||||
|
||||
@Override |
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
||||
User user = |
||||
userRepository |
||||
.findByUsername(username) |
||||
.orElseThrow( |
||||
() -> |
||||
new UsernameNotFoundException( |
||||
"No user found with username: " + username)); |
||||
|
||||
if (loginAttemptService.isBlocked(username)) { |
||||
throw new LockedException( |
||||
"Your account has been locked due to too many failed login attempts."); |
||||
} |
||||
|
||||
return new org.springframework.security.core.userdetails.User( |
||||
user.getUsername(), |
||||
user.getPassword(), |
||||
user.isEnabled(), |
||||
true, |
||||
true, |
||||
true, |
||||
getAuthorities(user.getAuthorities())); |
||||
} |
||||
|
||||
private Collection<? extends GrantedAuthority> getAuthorities(Set<Authority> authorities) { |
||||
return authorities.stream() |
||||
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
} |
||||
|
@ -1,140 +1,140 @@ |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Lazy; |
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; |
||||
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.configuration.EnableWebSecurity; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
||||
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
import org.springframework.security.web.SecurityFilterChain; |
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; |
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; |
||||
import org.springframework.security.web.savedrequest.NullRequestCache; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
|
||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl; |
||||
|
||||
@Configuration |
||||
@EnableWebSecurity() |
||||
@EnableMethodSecurity |
||||
public class SecurityConfiguration { |
||||
|
||||
@Autowired private UserDetailsService userDetailsService; |
||||
|
||||
@Bean |
||||
public PasswordEncoder passwordEncoder() { |
||||
return new BCryptPasswordEncoder(); |
||||
} |
||||
|
||||
@Autowired @Lazy private UserService userService; |
||||
|
||||
@Autowired |
||||
@Qualifier("loginEnabled") |
||||
public boolean loginEnabledValue; |
||||
|
||||
@Autowired private UserAuthenticationFilter userAuthenticationFilter; |
||||
|
||||
@Autowired private LoginAttemptService loginAttemptService; |
||||
|
||||
@Autowired private FirstLoginFilter firstLoginFilter; |
||||
|
||||
@Bean |
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
||||
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); |
||||
|
||||
if (loginEnabledValue) { |
||||
|
||||
http.csrf(csrf -> csrf.disable()); |
||||
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); |
||||
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); |
||||
http.formLogin( |
||||
formLogin -> |
||||
formLogin |
||||
.loginPage("/login") |
||||
.successHandler( |
||||
new CustomAuthenticationSuccessHandler()) |
||||
.defaultSuccessUrl("/") |
||||
.failureHandler( |
||||
new CustomAuthenticationFailureHandler( |
||||
loginAttemptService)) |
||||
.permitAll()) |
||||
.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())) |
||||
.logout( |
||||
logout -> |
||||
logout.logoutRequestMatcher( |
||||
new AntPathRequestMatcher("/logout")) |
||||
.logoutSuccessUrl("/login?logout=true") |
||||
.invalidateHttpSession(true) // Invalidate session
|
||||
.deleteCookies("JSESSIONID", "remember-me")) |
||||
.rememberMe( |
||||
rememberMeConfigurer -> |
||||
rememberMeConfigurer // Use the configurator directly
|
||||
.key("uniqueAndSecret") |
||||
.tokenRepository(persistentTokenRepository()) |
||||
.tokenValiditySeconds(1209600) // 2 weeks
|
||||
) |
||||
.authorizeHttpRequests( |
||||
authz -> |
||||
authz.requestMatchers( |
||||
req -> { |
||||
String uri = req.getRequestURI(); |
||||
String contextPath = req.getContextPath(); |
||||
|
||||
// Remove the context path from the URI
|
||||
String trimmedUri = |
||||
uri.startsWith(contextPath) |
||||
? uri.substring( |
||||
contextPath |
||||
.length()) |
||||
: uri; |
||||
|
||||
return trimmedUri.startsWith("/login") |
||||
|| trimmedUri.endsWith(".svg") |
||||
|| trimmedUri.startsWith( |
||||
"/register") |
||||
|| trimmedUri.startsWith("/error") |
||||
|| trimmedUri.startsWith("/images/") |
||||
|| trimmedUri.startsWith("/public/") |
||||
|| trimmedUri.startsWith("/css/") |
||||
|| trimmedUri.startsWith("/js/") |
||||
|| trimmedUri.startsWith( |
||||
"/api/v1/info/status"); |
||||
}) |
||||
.permitAll() |
||||
.anyRequest() |
||||
.authenticated()) |
||||
.userDetailsService(userDetailsService) |
||||
.authenticationProvider(authenticationProvider()); |
||||
} else { |
||||
http.csrf(csrf -> csrf.disable()) |
||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); |
||||
} |
||||
|
||||
return http.build(); |
||||
} |
||||
|
||||
@Bean |
||||
public IPRateLimitingFilter rateLimitingFilter() { |
||||
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
||||
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp); |
||||
} |
||||
|
||||
@Bean |
||||
public DaoAuthenticationProvider authenticationProvider() { |
||||
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); |
||||
authProvider.setUserDetailsService(userDetailsService); |
||||
authProvider.setPasswordEncoder(passwordEncoder()); |
||||
return authProvider; |
||||
} |
||||
|
||||
@Bean |
||||
public PersistentTokenRepository persistentTokenRepository() { |
||||
return new JPATokenRepositoryImpl(); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Lazy; |
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; |
||||
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.configuration.EnableWebSecurity; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
||||
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
import org.springframework.security.web.SecurityFilterChain; |
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; |
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; |
||||
import org.springframework.security.web.savedrequest.NullRequestCache; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
|
||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl; |
||||
|
||||
@Configuration |
||||
@EnableWebSecurity() |
||||
@EnableMethodSecurity |
||||
public class SecurityConfiguration { |
||||
|
||||
@Autowired private UserDetailsService userDetailsService; |
||||
|
||||
@Bean |
||||
public PasswordEncoder passwordEncoder() { |
||||
return new BCryptPasswordEncoder(); |
||||
} |
||||
|
||||
@Autowired @Lazy private UserService userService; |
||||
|
||||
@Autowired |
||||
@Qualifier("loginEnabled") |
||||
public boolean loginEnabledValue; |
||||
|
||||
@Autowired private UserAuthenticationFilter userAuthenticationFilter; |
||||
|
||||
@Autowired private LoginAttemptService loginAttemptService; |
||||
|
||||
@Autowired private FirstLoginFilter firstLoginFilter; |
||||
|
||||
@Bean |
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
||||
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); |
||||
|
||||
if (loginEnabledValue) { |
||||
|
||||
http.csrf(csrf -> csrf.disable()); |
||||
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); |
||||
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); |
||||
http.formLogin( |
||||
formLogin -> |
||||
formLogin |
||||
.loginPage("/login") |
||||
.successHandler( |
||||
new CustomAuthenticationSuccessHandler()) |
||||
.defaultSuccessUrl("/") |
||||
.failureHandler( |
||||
new CustomAuthenticationFailureHandler( |
||||
loginAttemptService)) |
||||
.permitAll()) |
||||
.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())) |
||||
.logout( |
||||
logout -> |
||||
logout.logoutRequestMatcher( |
||||
new AntPathRequestMatcher("/logout")) |
||||
.logoutSuccessUrl("/login?logout=true") |
||||
.invalidateHttpSession(true) // Invalidate session
|
||||
.deleteCookies("JSESSIONID", "remember-me")) |
||||
.rememberMe( |
||||
rememberMeConfigurer -> |
||||
rememberMeConfigurer // Use the configurator directly
|
||||
.key("uniqueAndSecret") |
||||
.tokenRepository(persistentTokenRepository()) |
||||
.tokenValiditySeconds(1209600) // 2 weeks
|
||||
) |
||||
.authorizeHttpRequests( |
||||
authz -> |
||||
authz.requestMatchers( |
||||
req -> { |
||||
String uri = req.getRequestURI(); |
||||
String contextPath = req.getContextPath(); |
||||
|
||||
// Remove the context path from the URI
|
||||
String trimmedUri = |
||||
uri.startsWith(contextPath) |
||||
? uri.substring( |
||||
contextPath |
||||
.length()) |
||||
: uri; |
||||
|
||||
return trimmedUri.startsWith("/login") |
||||
|| trimmedUri.endsWith(".svg") |
||||
|| trimmedUri.startsWith( |
||||
"/register") |
||||
|| trimmedUri.startsWith("/error") |
||||
|| trimmedUri.startsWith("/images/") |
||||
|| trimmedUri.startsWith("/public/") |
||||
|| trimmedUri.startsWith("/css/") |
||||
|| trimmedUri.startsWith("/js/") |
||||
|| trimmedUri.startsWith( |
||||
"/api/v1/info/status"); |
||||
}) |
||||
.permitAll() |
||||
.anyRequest() |
||||
.authenticated()) |
||||
.userDetailsService(userDetailsService) |
||||
.authenticationProvider(authenticationProvider()); |
||||
} else { |
||||
http.csrf(csrf -> csrf.disable()) |
||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); |
||||
} |
||||
|
||||
return http.build(); |
||||
} |
||||
|
||||
@Bean |
||||
public IPRateLimitingFilter rateLimitingFilter() { |
||||
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
||||
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp); |
||||
} |
||||
|
||||
@Bean |
||||
public DaoAuthenticationProvider authenticationProvider() { |
||||
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); |
||||
authProvider.setUserDetailsService(userDetailsService); |
||||
authProvider.setPasswordEncoder(passwordEncoder()); |
||||
return authProvider; |
||||
} |
||||
|
||||
@Bean |
||||
public PersistentTokenRepository persistentTokenRepository() { |
||||
return new JPATokenRepositoryImpl(); |
||||
} |
||||
} |
||||
|
@ -1,118 +1,118 @@ |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.context.annotation.Lazy; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
|
||||
import jakarta.servlet.FilterChain; |
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
import stirling.software.SPDF.model.ApiKeyAuthenticationToken; |
||||
|
||||
@Component |
||||
public class UserAuthenticationFilter extends OncePerRequestFilter { |
||||
|
||||
@Autowired private UserDetailsService userDetailsService; |
||||
|
||||
@Autowired @Lazy private UserService userService; |
||||
|
||||
@Autowired |
||||
@Qualifier("loginEnabled") |
||||
public boolean loginEnabledValue; |
||||
|
||||
@Override |
||||
protected void doFilterInternal( |
||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
|
||||
if (!loginEnabledValue) { |
||||
// If login is not enabled, just pass all requests without authentication
|
||||
filterChain.doFilter(request, response); |
||||
return; |
||||
} |
||||
String requestURI = request.getRequestURI(); |
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
||||
|
||||
// Check for API key in the request headers if no authentication exists
|
||||
if (authentication == null || !authentication.isAuthenticated()) { |
||||
String apiKey = request.getHeader("X-API-Key"); |
||||
if (apiKey != null && !apiKey.trim().isEmpty()) { |
||||
try { |
||||
// Use API key to authenticate. This requires you to have an authentication
|
||||
// provider for API keys.
|
||||
UserDetails userDetails = userService.loadUserByApiKey(apiKey); |
||||
if (userDetails == null) { |
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value()); |
||||
response.getWriter().write("Invalid API Key."); |
||||
return; |
||||
} |
||||
authentication = |
||||
new ApiKeyAuthenticationToken( |
||||
userDetails, apiKey, userDetails.getAuthorities()); |
||||
SecurityContextHolder.getContext().setAuthentication(authentication); |
||||
} catch (AuthenticationException e) { |
||||
// If API key authentication fails, deny the request
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value()); |
||||
response.getWriter().write("Invalid API Key."); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// If we still don't have any authentication, deny the request
|
||||
if (authentication == null || !authentication.isAuthenticated()) { |
||||
String method = request.getMethod(); |
||||
String contextPath = request.getContextPath(); |
||||
|
||||
if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) { |
||||
response.sendRedirect(contextPath + "/login"); // redirect to the login page
|
||||
return; |
||||
} else { |
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value()); |
||||
response.getWriter() |
||||
.write( |
||||
"Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected"); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
filterChain.doFilter(request, response); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { |
||||
String uri = request.getRequestURI(); |
||||
String contextPath = request.getContextPath(); |
||||
String[] permitAllPatterns = { |
||||
contextPath + "/login", |
||||
contextPath + "/register", |
||||
contextPath + "/error", |
||||
contextPath + "/images/", |
||||
contextPath + "/public/", |
||||
contextPath + "/css/", |
||||
contextPath + "/js/", |
||||
contextPath + "/pdfjs/", |
||||
contextPath + "/api/v1/info/status", |
||||
contextPath + "/site.webmanifest" |
||||
}; |
||||
|
||||
for (String pattern : permitAllPatterns) { |
||||
if (uri.startsWith(pattern) || uri.endsWith(".svg")) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.context.annotation.Lazy; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
|
||||
import jakarta.servlet.FilterChain; |
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
import stirling.software.SPDF.model.ApiKeyAuthenticationToken; |
||||
|
||||
@Component |
||||
public class UserAuthenticationFilter extends OncePerRequestFilter { |
||||
|
||||
@Autowired private UserDetailsService userDetailsService; |
||||
|
||||
@Autowired @Lazy private UserService userService; |
||||
|
||||
@Autowired |
||||
@Qualifier("loginEnabled") |
||||
public boolean loginEnabledValue; |
||||
|
||||
@Override |
||||
protected void doFilterInternal( |
||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
|
||||
if (!loginEnabledValue) { |
||||
// If login is not enabled, just pass all requests without authentication
|
||||
filterChain.doFilter(request, response); |
||||
return; |
||||
} |
||||
String requestURI = request.getRequestURI(); |
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
||||
|
||||
// Check for API key in the request headers if no authentication exists
|
||||
if (authentication == null || !authentication.isAuthenticated()) { |
||||
String apiKey = request.getHeader("X-API-Key"); |
||||
if (apiKey != null && !apiKey.trim().isEmpty()) { |
||||
try { |
||||
// Use API key to authenticate. This requires you to have an authentication
|
||||
// provider for API keys.
|
||||
UserDetails userDetails = userService.loadUserByApiKey(apiKey); |
||||
if (userDetails == null) { |
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value()); |
||||
response.getWriter().write("Invalid API Key."); |
||||
return; |
||||
} |
||||
authentication = |
||||
new ApiKeyAuthenticationToken( |
||||
userDetails, apiKey, userDetails.getAuthorities()); |
||||
SecurityContextHolder.getContext().setAuthentication(authentication); |
||||
} catch (AuthenticationException e) { |
||||
// If API key authentication fails, deny the request
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value()); |
||||
response.getWriter().write("Invalid API Key."); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// If we still don't have any authentication, deny the request
|
||||
if (authentication == null || !authentication.isAuthenticated()) { |
||||
String method = request.getMethod(); |
||||
String contextPath = request.getContextPath(); |
||||
|
||||
if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) { |
||||
response.sendRedirect(contextPath + "/login"); // redirect to the login page
|
||||
return; |
||||
} else { |
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value()); |
||||
response.getWriter() |
||||
.write( |
||||
"Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected"); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
filterChain.doFilter(request, response); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { |
||||
String uri = request.getRequestURI(); |
||||
String contextPath = request.getContextPath(); |
||||
String[] permitAllPatterns = { |
||||
contextPath + "/login", |
||||
contextPath + "/register", |
||||
contextPath + "/error", |
||||
contextPath + "/images/", |
||||
contextPath + "/public/", |
||||
contextPath + "/css/", |
||||
contextPath + "/js/", |
||||
contextPath + "/pdfjs/", |
||||
contextPath + "/api/v1/info/status", |
||||
contextPath + "/site.webmanifest" |
||||
}; |
||||
|
||||
for (String pattern : permitAllPatterns) { |
||||
if (uri.startsWith(pattern) || uri.endsWith(".svg")) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
|
@ -1,143 +1,143 @@ |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.io.IOException; |
||||
import java.time.Duration; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
|
||||
import io.github.bucket4j.Bandwidth; |
||||
import io.github.bucket4j.Bucket; |
||||
import io.github.bucket4j.ConsumptionProbe; |
||||
import io.github.bucket4j.Refill; |
||||
|
||||
import jakarta.servlet.FilterChain; |
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
import stirling.software.SPDF.model.Role; |
||||
|
||||
@Component |
||||
public class UserBasedRateLimitingFilter extends OncePerRequestFilter { |
||||
|
||||
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>(); |
||||
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>(); |
||||
|
||||
@Autowired private UserDetailsService userDetailsService; |
||||
|
||||
@Autowired |
||||
@Qualifier("rateLimit") |
||||
public boolean rateLimit; |
||||
|
||||
@Override |
||||
protected void doFilterInternal( |
||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
if (!rateLimit) { |
||||
// If rateLimit is not enabled, just pass all requests without rate limiting
|
||||
filterChain.doFilter(request, response); |
||||
return; |
||||
} |
||||
|
||||
String method = request.getMethod(); |
||||
if (!"POST".equalsIgnoreCase(method)) { |
||||
// If the request is not a POST, just pass it through without rate limiting
|
||||
filterChain.doFilter(request, response); |
||||
return; |
||||
} |
||||
|
||||
String identifier = null; |
||||
|
||||
// Check for API key in the request headers
|
||||
String apiKey = request.getHeader("X-API-Key"); |
||||
if (apiKey != null && !apiKey.trim().isEmpty()) { |
||||
identifier = |
||||
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
||||
} else { |
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
||||
if (authentication != null && authentication.isAuthenticated()) { |
||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal(); |
||||
identifier = userDetails.getUsername(); |
||||
} |
||||
} |
||||
|
||||
// If neither API key nor an authenticated user is present, use IP address
|
||||
if (identifier == null) { |
||||
identifier = request.getRemoteAddr(); |
||||
} |
||||
|
||||
Role userRole = |
||||
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); |
||||
|
||||
if (request.getHeader("X-API-Key") != null) { |
||||
// It's an API call
|
||||
processRequest( |
||||
userRole.getApiCallsPerDay(), |
||||
identifier, |
||||
apiBuckets, |
||||
request, |
||||
response, |
||||
filterChain); |
||||
} else { |
||||
// It's a Web UI call
|
||||
processRequest( |
||||
userRole.getWebCallsPerDay(), |
||||
identifier, |
||||
webBuckets, |
||||
request, |
||||
response, |
||||
filterChain); |
||||
} |
||||
} |
||||
|
||||
private Role getRoleFromAuthentication(Authentication authentication) { |
||||
if (authentication != null && authentication.isAuthenticated()) { |
||||
for (GrantedAuthority authority : authentication.getAuthorities()) { |
||||
try { |
||||
return Role.fromString(authority.getAuthority()); |
||||
} catch (IllegalArgumentException ex) { |
||||
// Ignore and continue to next authority.
|
||||
} |
||||
} |
||||
} |
||||
throw new IllegalStateException("User does not have a valid role."); |
||||
} |
||||
|
||||
private void processRequest( |
||||
int limitPerDay, |
||||
String identifier, |
||||
Map<String, Bucket> buckets, |
||||
HttpServletRequest request, |
||||
HttpServletResponse response, |
||||
FilterChain filterChain) |
||||
throws IOException, ServletException { |
||||
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay)); |
||||
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1); |
||||
|
||||
if (probe.isConsumed()) { |
||||
response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens())); |
||||
filterChain.doFilter(request, response); |
||||
} else { |
||||
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000; |
||||
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); |
||||
response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill)); |
||||
response.getWriter().write("Rate limit exceeded for POST requests."); |
||||
} |
||||
} |
||||
|
||||
private Bucket createUserBucket(int limitPerDay) { |
||||
Bandwidth limit = |
||||
Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1))); |
||||
return Bucket.builder().addLimit(limit).build(); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.io.IOException; |
||||
import java.time.Duration; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.annotation.Qualifier; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.web.filter.OncePerRequestFilter; |
||||
|
||||
import io.github.bucket4j.Bandwidth; |
||||
import io.github.bucket4j.Bucket; |
||||
import io.github.bucket4j.ConsumptionProbe; |
||||
import io.github.bucket4j.Refill; |
||||
|
||||
import jakarta.servlet.FilterChain; |
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
import stirling.software.SPDF.model.Role; |
||||
|
||||
@Component |
||||
public class UserBasedRateLimitingFilter extends OncePerRequestFilter { |
||||
|
||||
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>(); |
||||
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>(); |
||||
|
||||
@Autowired private UserDetailsService userDetailsService; |
||||
|
||||
@Autowired |
||||
@Qualifier("rateLimit") |
||||
public boolean rateLimit; |
||||
|
||||
@Override |
||||
protected void doFilterInternal( |
||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||
throws ServletException, IOException { |
||||
if (!rateLimit) { |
||||
// If rateLimit is not enabled, just pass all requests without rate limiting
|
||||
filterChain.doFilter(request, response); |
||||
return; |
||||
} |
||||
|
||||
String method = request.getMethod(); |
||||
if (!"POST".equalsIgnoreCase(method)) { |
||||
// If the request is not a POST, just pass it through without rate limiting
|
||||
filterChain.doFilter(request, response); |
||||
return; |
||||
} |
||||
|
||||
String identifier = null; |
||||
|
||||
// Check for API key in the request headers
|
||||
String apiKey = request.getHeader("X-API-Key"); |
||||
if (apiKey != null && !apiKey.trim().isEmpty()) { |
||||
identifier = |
||||
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
||||
} else { |
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
||||
if (authentication != null && authentication.isAuthenticated()) { |
||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal(); |
||||
identifier = userDetails.getUsername(); |
||||
} |
||||
} |
||||
|
||||
// If neither API key nor an authenticated user is present, use IP address
|
||||
if (identifier == null) { |
||||
identifier = request.getRemoteAddr(); |
||||
} |
||||
|
||||
Role userRole = |
||||
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); |
||||
|
||||
if (request.getHeader("X-API-Key") != null) { |
||||
// It's an API call
|
||||
processRequest( |
||||
userRole.getApiCallsPerDay(), |
||||
identifier, |
||||
apiBuckets, |
||||
request, |
||||
response, |
||||
filterChain); |
||||
} else { |
||||
// It's a Web UI call
|
||||
processRequest( |
||||
userRole.getWebCallsPerDay(), |
||||
identifier, |
||||
webBuckets, |
||||
request, |
||||
response, |
||||
filterChain); |
||||
} |
||||
} |
||||
|
||||
private Role getRoleFromAuthentication(Authentication authentication) { |
||||
if (authentication != null && authentication.isAuthenticated()) { |
||||
for (GrantedAuthority authority : authentication.getAuthorities()) { |
||||
try { |
||||
return Role.fromString(authority.getAuthority()); |
||||
} catch (IllegalArgumentException ex) { |
||||
// Ignore and continue to next authority.
|
||||
} |
||||
} |
||||
} |
||||
throw new IllegalStateException("User does not have a valid role."); |
||||
} |
||||
|
||||
private void processRequest( |
||||
int limitPerDay, |
||||
String identifier, |
||||
Map<String, Bucket> buckets, |
||||
HttpServletRequest request, |
||||
HttpServletResponse response, |
||||
FilterChain filterChain) |
||||
throws IOException, ServletException { |
||||
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay)); |
||||
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1); |
||||
|
||||
if (probe.isConsumed()) { |
||||
response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens())); |
||||
filterChain.doFilter(request, response); |
||||
} else { |
||||
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000; |
||||
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); |
||||
response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill)); |
||||
response.getWriter().write("Rate limit exceeded for POST requests."); |
||||
} |
||||
} |
||||
|
||||
private Bucket createUserBucket(int limitPerDay) { |
||||
Bandwidth limit = |
||||
Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1))); |
||||
return Bucket.builder().addLimit(limit).build(); |
||||
} |
||||
} |
||||
|
@ -1,197 +1,197 @@ |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.UUID; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; |
||||
import stirling.software.SPDF.model.Authority; |
||||
import stirling.software.SPDF.model.Role; |
||||
import stirling.software.SPDF.model.User; |
||||
import stirling.software.SPDF.repository.UserRepository; |
||||
|
||||
@Service |
||||
public class UserService implements UserServiceInterface { |
||||
|
||||
@Autowired private UserRepository userRepository; |
||||
|
||||
@Autowired private PasswordEncoder passwordEncoder; |
||||
|
||||
public Authentication getAuthentication(String apiKey) { |
||||
User user = getUserByApiKey(apiKey); |
||||
if (user == null) { |
||||
throw new UsernameNotFoundException("API key is not valid"); |
||||
} |
||||
|
||||
// Convert the user into an Authentication object
|
||||
return new UsernamePasswordAuthenticationToken( |
||||
user, // principal (typically the user)
|
||||
null, // credentials (we don't expose the password or API key here)
|
||||
getAuthorities(user) // user's authorities (roles/permissions)
|
||||
); |
||||
} |
||||
|
||||
private Collection<? extends GrantedAuthority> getAuthorities(User user) { |
||||
// Convert each Authority object into a SimpleGrantedAuthority object.
|
||||
return user.getAuthorities().stream() |
||||
.map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority())) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
private String generateApiKey() { |
||||
String apiKey; |
||||
do { |
||||
apiKey = UUID.randomUUID().toString(); |
||||
} while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness
|
||||
return apiKey; |
||||
} |
||||
|
||||
public User addApiKeyToUser(String username) { |
||||
User user = |
||||
userRepository |
||||
.findByUsername(username) |
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found")); |
||||
|
||||
user.setApiKey(generateApiKey()); |
||||
return userRepository.save(user); |
||||
} |
||||
|
||||
public User refreshApiKeyForUser(String username) { |
||||
return addApiKeyToUser(username); // reuse the add API key method for refreshing
|
||||
} |
||||
|
||||
public String getApiKeyForUser(String username) { |
||||
User user = |
||||
userRepository |
||||
.findByUsername(username) |
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found")); |
||||
return user.getApiKey(); |
||||
} |
||||
|
||||
public boolean isValidApiKey(String apiKey) { |
||||
return userRepository.findByApiKey(apiKey) != null; |
||||
} |
||||
|
||||
public User getUserByApiKey(String apiKey) { |
||||
return userRepository.findByApiKey(apiKey); |
||||
} |
||||
|
||||
public UserDetails loadUserByApiKey(String apiKey) { |
||||
User userOptional = userRepository.findByApiKey(apiKey); |
||||
if (userOptional != null) { |
||||
User user = userOptional; |
||||
// Convert your User entity to a UserDetails object with authorities
|
||||
return new org.springframework.security.core.userdetails.User( |
||||
user.getUsername(), |
||||
user.getPassword(), // you might not need this for API key auth
|
||||
getAuthorities(user)); |
||||
} |
||||
return null; // or throw an exception
|
||||
} |
||||
|
||||
public boolean validateApiKeyForUser(String username, String apiKey) { |
||||
Optional<User> userOpt = userRepository.findByUsername(username); |
||||
return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey); |
||||
} |
||||
|
||||
public void saveUser(String username, String password) { |
||||
User user = new User(); |
||||
user.setUsername(username); |
||||
user.setPassword(passwordEncoder.encode(password)); |
||||
user.setEnabled(true); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void saveUser(String username, String password, String role, boolean firstLogin) { |
||||
User user = new User(); |
||||
user.setUsername(username); |
||||
user.setPassword(passwordEncoder.encode(password)); |
||||
user.addAuthority(new Authority(role, user)); |
||||
user.setEnabled(true); |
||||
user.setFirstLogin(firstLogin); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void saveUser(String username, String password, String role) { |
||||
User user = new User(); |
||||
user.setUsername(username); |
||||
user.setPassword(passwordEncoder.encode(password)); |
||||
user.addAuthority(new Authority(role, user)); |
||||
user.setEnabled(true); |
||||
user.setFirstLogin(false); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void deleteUser(String username) { |
||||
Optional<User> userOpt = userRepository.findByUsername(username); |
||||
if (userOpt.isPresent()) { |
||||
for (Authority authority : userOpt.get().getAuthorities()) { |
||||
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { |
||||
return; |
||||
} |
||||
} |
||||
userRepository.delete(userOpt.get()); |
||||
} |
||||
} |
||||
|
||||
public boolean usernameExists(String username) { |
||||
return userRepository.findByUsername(username).isPresent(); |
||||
} |
||||
|
||||
public boolean hasUsers() { |
||||
return userRepository.count() > 0; |
||||
} |
||||
|
||||
public void updateUserSettings(String username, Map<String, String> updates) { |
||||
Optional<User> userOpt = userRepository.findByUsername(username); |
||||
if (userOpt.isPresent()) { |
||||
User user = userOpt.get(); |
||||
Map<String, String> settingsMap = user.getSettings(); |
||||
|
||||
if (settingsMap == null) { |
||||
settingsMap = new HashMap<String, String>(); |
||||
} |
||||
settingsMap.clear(); |
||||
settingsMap.putAll(updates); |
||||
user.setSettings(settingsMap); |
||||
|
||||
userRepository.save(user); |
||||
} |
||||
} |
||||
|
||||
public Optional<User> findByUsername(String username) { |
||||
return userRepository.findByUsername(username); |
||||
} |
||||
|
||||
public void changeUsername(User user, String newUsername) { |
||||
user.setUsername(newUsername); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void changePassword(User user, String newPassword) { |
||||
user.setPassword(passwordEncoder.encode(newPassword)); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void changeFirstUse(User user, boolean firstUse) { |
||||
user.setFirstLogin(firstUse); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public boolean isPasswordCorrect(User user, String currentPassword) { |
||||
return passwordEncoder.matches(currentPassword, user.getPassword()); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.config.security; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.UUID; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; |
||||
import stirling.software.SPDF.model.Authority; |
||||
import stirling.software.SPDF.model.Role; |
||||
import stirling.software.SPDF.model.User; |
||||
import stirling.software.SPDF.repository.UserRepository; |
||||
|
||||
@Service |
||||
public class UserService implements UserServiceInterface { |
||||
|
||||
@Autowired private UserRepository userRepository; |
||||
|
||||
@Autowired private PasswordEncoder passwordEncoder; |
||||
|
||||
public Authentication getAuthentication(String apiKey) { |
||||
User user = getUserByApiKey(apiKey); |
||||
if (user == null) { |
||||
throw new UsernameNotFoundException("API key is not valid"); |
||||
} |
||||
|
||||
// Convert the user into an Authentication object
|
||||
return new UsernamePasswordAuthenticationToken( |
||||
user, // principal (typically the user)
|
||||
null, // credentials (we don't expose the password or API key here)
|
||||
getAuthorities(user) // user's authorities (roles/permissions)
|
||||
); |
||||
} |
||||
|
||||
private Collection<? extends GrantedAuthority> getAuthorities(User user) { |
||||
// Convert each Authority object into a SimpleGrantedAuthority object.
|
||||
return user.getAuthorities().stream() |
||||
.map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority())) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
private String generateApiKey() { |
||||
String apiKey; |
||||
do { |
||||
apiKey = UUID.randomUUID().toString(); |
||||
} while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness
|
||||
return apiKey; |
||||
} |
||||
|
||||
public User addApiKeyToUser(String username) { |
||||
User user = |
||||
userRepository |
||||
.findByUsername(username) |
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found")); |
||||
|
||||
user.setApiKey(generateApiKey()); |
||||
return userRepository.save(user); |
||||
} |
||||
|
||||
public User refreshApiKeyForUser(String username) { |
||||
return addApiKeyToUser(username); // reuse the add API key method for refreshing
|
||||
} |
||||
|
||||
public String getApiKeyForUser(String username) { |
||||
User user = |
||||
userRepository |
||||
.findByUsername(username) |
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found")); |
||||
return user.getApiKey(); |
||||
} |
||||
|
||||
public boolean isValidApiKey(String apiKey) { |
||||
return userRepository.findByApiKey(apiKey) != null; |
||||
} |
||||
|
||||
public User getUserByApiKey(String apiKey) { |
||||
return userRepository.findByApiKey(apiKey); |
||||
} |
||||
|
||||
public UserDetails loadUserByApiKey(String apiKey) { |
||||
User userOptional = userRepository.findByApiKey(apiKey); |
||||
if (userOptional != null) { |
||||
User user = userOptional; |
||||
// Convert your User entity to a UserDetails object with authorities
|
||||
return new org.springframework.security.core.userdetails.User( |
||||
user.getUsername(), |
||||
user.getPassword(), // you might not need this for API key auth
|
||||
getAuthorities(user)); |
||||
} |
||||
return null; // or throw an exception
|
||||
} |
||||
|
||||
public boolean validateApiKeyForUser(String username, String apiKey) { |
||||
Optional<User> userOpt = userRepository.findByUsername(username); |
||||
return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey); |
||||
} |
||||
|
||||
public void saveUser(String username, String password) { |
||||
User user = new User(); |
||||
user.setUsername(username); |
||||
user.setPassword(passwordEncoder.encode(password)); |
||||
user.setEnabled(true); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void saveUser(String username, String password, String role, boolean firstLogin) { |
||||
User user = new User(); |
||||
user.setUsername(username); |
||||
user.setPassword(passwordEncoder.encode(password)); |
||||
user.addAuthority(new Authority(role, user)); |
||||
user.setEnabled(true); |
||||
user.setFirstLogin(firstLogin); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void saveUser(String username, String password, String role) { |
||||
User user = new User(); |
||||
user.setUsername(username); |
||||
user.setPassword(passwordEncoder.encode(password)); |
||||
user.addAuthority(new Authority(role, user)); |
||||
user.setEnabled(true); |
||||
user.setFirstLogin(false); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void deleteUser(String username) { |
||||
Optional<User> userOpt = userRepository.findByUsername(username); |
||||
if (userOpt.isPresent()) { |
||||
for (Authority authority : userOpt.get().getAuthorities()) { |
||||
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { |
||||
return; |
||||
} |
||||
} |
||||
userRepository.delete(userOpt.get()); |
||||
} |
||||
} |
||||
|
||||
public boolean usernameExists(String username) { |
||||
return userRepository.findByUsername(username).isPresent(); |
||||
} |
||||
|
||||
public boolean hasUsers() { |
||||
return userRepository.count() > 0; |
||||
} |
||||
|
||||
public void updateUserSettings(String username, Map<String, String> updates) { |
||||
Optional<User> userOpt = userRepository.findByUsername(username); |
||||
if (userOpt.isPresent()) { |
||||
User user = userOpt.get(); |
||||
Map<String, String> settingsMap = user.getSettings(); |
||||
|
||||
if (settingsMap == null) { |
||||
settingsMap = new HashMap<String, String>(); |
||||
} |
||||
settingsMap.clear(); |
||||
settingsMap.putAll(updates); |
||||
user.setSettings(settingsMap); |
||||
|
||||
userRepository.save(user); |
||||
} |
||||
} |
||||
|
||||
public Optional<User> findByUsername(String username) { |
||||
return userRepository.findByUsername(username); |
||||
} |
||||
|
||||
public void changeUsername(User user, String newUsername) { |
||||
user.setUsername(newUsername); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void changePassword(User user, String newPassword) { |
||||
user.setPassword(passwordEncoder.encode(newPassword)); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public void changeFirstUse(User user, boolean firstUse) { |
||||
user.setFirstLogin(firstUse); |
||||
userRepository.save(user); |
||||
} |
||||
|
||||
public boolean isPasswordCorrect(User user, String currentPassword) { |
||||
return passwordEncoder.matches(currentPassword, user.getPassword()); |
||||
} |
||||
} |
||||
|
@ -1,77 +1,77 @@ |
||||
package stirling.software.SPDF.controller.api.converters; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
import io.swagger.v3.oas.annotations.Operation; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; |
||||
import stirling.software.SPDF.utils.GeneralUtils; |
||||
import stirling.software.SPDF.utils.ProcessExecutor; |
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@Tag(name = "Convert", description = "Convert APIs") |
||||
@RequestMapping("/api/v1/convert") |
||||
public class ConvertWebsiteToPDF { |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf") |
||||
@Operation( |
||||
summary = "Convert a URL to a PDF", |
||||
description = |
||||
"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) |
||||
throws IOException, InterruptedException { |
||||
String URL = request.getUrlInput(); |
||||
|
||||
// Validate the URL format
|
||||
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) { |
||||
throw new IllegalArgumentException("Invalid URL format provided."); |
||||
} |
||||
Path tempOutputFile = null; |
||||
byte[] pdfBytes; |
||||
try { |
||||
// Prepare the output file path
|
||||
tempOutputFile = Files.createTempFile("output_", ".pdf"); |
||||
|
||||
// Prepare the OCRmyPDF command
|
||||
List<String> command = new ArrayList<>(); |
||||
command.add("weasyprint"); |
||||
command.add(URL); |
||||
command.add(tempOutputFile.toString()); |
||||
|
||||
ProcessExecutorResult returnCode = |
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) |
||||
.runCommandWithOutputHandling(command); |
||||
|
||||
// Read the optimized PDF file
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile); |
||||
} finally { |
||||
// Clean up the temporary files
|
||||
Files.delete(tempOutputFile); |
||||
} |
||||
// Convert URL to a safe filename
|
||||
String outputFilename = convertURLToFileName(URL); |
||||
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); |
||||
} |
||||
|
||||
private String convertURLToFileName(String url) { |
||||
String safeName = url.replaceAll("[^a-zA-Z0-9]", "_"); |
||||
if (safeName.length() > 50) { |
||||
safeName = safeName.substring(0, 50); // restrict to 50 characters
|
||||
} |
||||
return safeName + ".pdf"; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.controller.api.converters; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
import io.swagger.v3.oas.annotations.Operation; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest; |
||||
import stirling.software.SPDF.utils.GeneralUtils; |
||||
import stirling.software.SPDF.utils.ProcessExecutor; |
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@Tag(name = "Convert", description = "Convert APIs") |
||||
@RequestMapping("/api/v1/convert") |
||||
public class ConvertWebsiteToPDF { |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf") |
||||
@Operation( |
||||
summary = "Convert a URL to a PDF", |
||||
description = |
||||
"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) |
||||
throws IOException, InterruptedException { |
||||
String URL = request.getUrlInput(); |
||||
|
||||
// Validate the URL format
|
||||
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) { |
||||
throw new IllegalArgumentException("Invalid URL format provided."); |
||||
} |
||||
Path tempOutputFile = null; |
||||
byte[] pdfBytes; |
||||
try { |
||||
// Prepare the output file path
|
||||
tempOutputFile = Files.createTempFile("output_", ".pdf"); |
||||
|
||||
// Prepare the OCRmyPDF command
|
||||
List<String> command = new ArrayList<>(); |
||||
command.add("weasyprint"); |
||||
command.add(URL); |
||||
command.add(tempOutputFile.toString()); |
||||
|
||||
ProcessExecutorResult returnCode = |
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) |
||||
.runCommandWithOutputHandling(command); |
||||
|
||||
// Read the optimized PDF file
|
||||
pdfBytes = Files.readAllBytes(tempOutputFile); |
||||
} finally { |
||||
// Clean up the temporary files
|
||||
Files.delete(tempOutputFile); |
||||
} |
||||
// Convert URL to a safe filename
|
||||
String outputFilename = convertURLToFileName(URL); |
||||
|
||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); |
||||
} |
||||
|
||||
private String convertURLToFileName(String url) { |
||||
String safeName = url.replaceAll("[^a-zA-Z0-9]", "_"); |
||||
if (safeName.length() > 50) { |
||||
safeName = safeName.substring(0, 50); // restrict to 50 characters
|
||||
} |
||||
return safeName + ".pdf"; |
||||
} |
||||
} |
||||
|
@ -1,210 +1,210 @@ |
||||
package stirling.software.SPDF.controller.api.filters; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.apache.pdfbox.pdmodel.PDPage; |
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import io.swagger.v3.oas.annotations.Operation; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.api.PDFComparisonAndCount; |
||||
import stirling.software.SPDF.model.api.PDFWithPageNums; |
||||
import stirling.software.SPDF.model.api.filter.ContainsTextRequest; |
||||
import stirling.software.SPDF.model.api.filter.FileSizeRequest; |
||||
import stirling.software.SPDF.model.api.filter.PageRotationRequest; |
||||
import stirling.software.SPDF.model.api.filter.PageSizeRequest; |
||||
import stirling.software.SPDF.utils.PdfUtils; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@RequestMapping("/api/v1/filter") |
||||
@Tag(name = "Filter", description = "Filter APIs") |
||||
public class FilterController { |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text") |
||||
@Operation( |
||||
summary = "Checks if a PDF contains set text, returns true if does", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String text = request.getText(); |
||||
String pageNumber = request.getPageNumbers(); |
||||
|
||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); |
||||
if (PdfUtils.hasText(pdfDocument, pageNumber, text)) |
||||
return WebResponseUtils.pdfDocToWebResponse( |
||||
pdfDocument, inputFile.getOriginalFilename()); |
||||
return null; |
||||
} |
||||
|
||||
// TODO
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image") |
||||
@Operation( |
||||
summary = "Checks if a PDF contains an image", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String pageNumber = request.getPageNumbers(); |
||||
|
||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); |
||||
if (PdfUtils.hasImages(pdfDocument, pageNumber)) |
||||
return WebResponseUtils.pdfDocToWebResponse( |
||||
pdfDocument, inputFile.getOriginalFilename()); |
||||
return null; |
||||
} |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count") |
||||
@Operation( |
||||
summary = "Checks if a PDF is greater, less or equal to a setPageCount", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String pageCount = request.getPageCount(); |
||||
String comparator = request.getComparator(); |
||||
// Load the PDF
|
||||
PDDocument document = PDDocument.load(inputFile.getInputStream()); |
||||
int actualPageCount = document.getNumberOfPages(); |
||||
|
||||
boolean valid = false; |
||||
// Perform the comparison
|
||||
switch (comparator) { |
||||
case "Greater": |
||||
valid = actualPageCount > Integer.parseInt(pageCount); |
||||
break; |
||||
case "Equal": |
||||
valid = actualPageCount == Integer.parseInt(pageCount); |
||||
break; |
||||
case "Less": |
||||
valid = actualPageCount < Integer.parseInt(pageCount); |
||||
break; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid comparator: " + comparator); |
||||
} |
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); |
||||
return null; |
||||
} |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size") |
||||
@Operation( |
||||
summary = "Checks if a PDF is of a certain size", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String standardPageSize = request.getStandardPageSize(); |
||||
String comparator = request.getComparator(); |
||||
|
||||
// Load the PDF
|
||||
PDDocument document = PDDocument.load(inputFile.getInputStream()); |
||||
|
||||
PDPage firstPage = document.getPage(0); |
||||
PDRectangle actualPageSize = firstPage.getMediaBox(); |
||||
|
||||
// Calculate the area of the actual page size
|
||||
float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight(); |
||||
|
||||
// Get the standard size and calculate its area
|
||||
PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize); |
||||
float standardArea = standardSize.getWidth() * standardSize.getHeight(); |
||||
|
||||
boolean valid = false; |
||||
// Perform the comparison
|
||||
switch (comparator) { |
||||
case "Greater": |
||||
valid = actualArea > standardArea; |
||||
break; |
||||
case "Equal": |
||||
valid = actualArea == standardArea; |
||||
break; |
||||
case "Less": |
||||
valid = actualArea < standardArea; |
||||
break; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid comparator: " + comparator); |
||||
} |
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); |
||||
return null; |
||||
} |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size") |
||||
@Operation( |
||||
summary = "Checks if a PDF is a set file size", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String fileSize = request.getFileSize(); |
||||
String comparator = request.getComparator(); |
||||
|
||||
// Get the file size
|
||||
long actualFileSize = inputFile.getSize(); |
||||
|
||||
boolean valid = false; |
||||
// Perform the comparison
|
||||
switch (comparator) { |
||||
case "Greater": |
||||
valid = actualFileSize > Long.parseLong(fileSize); |
||||
break; |
||||
case "Equal": |
||||
valid = actualFileSize == Long.parseLong(fileSize); |
||||
break; |
||||
case "Less": |
||||
valid = actualFileSize < Long.parseLong(fileSize); |
||||
break; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid comparator: " + comparator); |
||||
} |
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); |
||||
return null; |
||||
} |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation") |
||||
@Operation( |
||||
summary = "Checks if a PDF is of a certain rotation", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
int rotation = request.getRotation(); |
||||
String comparator = request.getComparator(); |
||||
|
||||
// Load the PDF
|
||||
PDDocument document = PDDocument.load(inputFile.getInputStream()); |
||||
|
||||
// Get the rotation of the first page
|
||||
PDPage firstPage = document.getPage(0); |
||||
int actualRotation = firstPage.getRotation(); |
||||
boolean valid = false; |
||||
// Perform the comparison
|
||||
switch (comparator) { |
||||
case "Greater": |
||||
valid = actualRotation > rotation; |
||||
break; |
||||
case "Equal": |
||||
valid = actualRotation == rotation; |
||||
break; |
||||
case "Less": |
||||
valid = actualRotation < rotation; |
||||
break; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid comparator: " + comparator); |
||||
} |
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); |
||||
return null; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.controller.api.filters; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.apache.pdfbox.pdmodel.PDPage; |
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import io.swagger.v3.oas.annotations.Operation; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.api.PDFComparisonAndCount; |
||||
import stirling.software.SPDF.model.api.PDFWithPageNums; |
||||
import stirling.software.SPDF.model.api.filter.ContainsTextRequest; |
||||
import stirling.software.SPDF.model.api.filter.FileSizeRequest; |
||||
import stirling.software.SPDF.model.api.filter.PageRotationRequest; |
||||
import stirling.software.SPDF.model.api.filter.PageSizeRequest; |
||||
import stirling.software.SPDF.utils.PdfUtils; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@RequestMapping("/api/v1/filter") |
||||
@Tag(name = "Filter", description = "Filter APIs") |
||||
public class FilterController { |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text") |
||||
@Operation( |
||||
summary = "Checks if a PDF contains set text, returns true if does", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String text = request.getText(); |
||||
String pageNumber = request.getPageNumbers(); |
||||
|
||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); |
||||
if (PdfUtils.hasText(pdfDocument, pageNumber, text)) |
||||
return WebResponseUtils.pdfDocToWebResponse( |
||||
pdfDocument, inputFile.getOriginalFilename()); |
||||
return null; |
||||
} |
||||
|
||||
// TODO
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image") |
||||
@Operation( |
||||
summary = "Checks if a PDF contains an image", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String pageNumber = request.getPageNumbers(); |
||||
|
||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream()); |
||||
if (PdfUtils.hasImages(pdfDocument, pageNumber)) |
||||
return WebResponseUtils.pdfDocToWebResponse( |
||||
pdfDocument, inputFile.getOriginalFilename()); |
||||
return null; |
||||
} |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count") |
||||
@Operation( |
||||
summary = "Checks if a PDF is greater, less or equal to a setPageCount", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String pageCount = request.getPageCount(); |
||||
String comparator = request.getComparator(); |
||||
// Load the PDF
|
||||
PDDocument document = PDDocument.load(inputFile.getInputStream()); |
||||
int actualPageCount = document.getNumberOfPages(); |
||||
|
||||
boolean valid = false; |
||||
// Perform the comparison
|
||||
switch (comparator) { |
||||
case "Greater": |
||||
valid = actualPageCount > Integer.parseInt(pageCount); |
||||
break; |
||||
case "Equal": |
||||
valid = actualPageCount == Integer.parseInt(pageCount); |
||||
break; |
||||
case "Less": |
||||
valid = actualPageCount < Integer.parseInt(pageCount); |
||||
break; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid comparator: " + comparator); |
||||
} |
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); |
||||
return null; |
||||
} |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size") |
||||
@Operation( |
||||
summary = "Checks if a PDF is of a certain size", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String standardPageSize = request.getStandardPageSize(); |
||||
String comparator = request.getComparator(); |
||||
|
||||
// Load the PDF
|
||||
PDDocument document = PDDocument.load(inputFile.getInputStream()); |
||||
|
||||
PDPage firstPage = document.getPage(0); |
||||
PDRectangle actualPageSize = firstPage.getMediaBox(); |
||||
|
||||
// Calculate the area of the actual page size
|
||||
float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight(); |
||||
|
||||
// Get the standard size and calculate its area
|
||||
PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize); |
||||
float standardArea = standardSize.getWidth() * standardSize.getHeight(); |
||||
|
||||
boolean valid = false; |
||||
// Perform the comparison
|
||||
switch (comparator) { |
||||
case "Greater": |
||||
valid = actualArea > standardArea; |
||||
break; |
||||
case "Equal": |
||||
valid = actualArea == standardArea; |
||||
break; |
||||
case "Less": |
||||
valid = actualArea < standardArea; |
||||
break; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid comparator: " + comparator); |
||||
} |
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); |
||||
return null; |
||||
} |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size") |
||||
@Operation( |
||||
summary = "Checks if a PDF is a set file size", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
String fileSize = request.getFileSize(); |
||||
String comparator = request.getComparator(); |
||||
|
||||
// Get the file size
|
||||
long actualFileSize = inputFile.getSize(); |
||||
|
||||
boolean valid = false; |
||||
// Perform the comparison
|
||||
switch (comparator) { |
||||
case "Greater": |
||||
valid = actualFileSize > Long.parseLong(fileSize); |
||||
break; |
||||
case "Equal": |
||||
valid = actualFileSize == Long.parseLong(fileSize); |
||||
break; |
||||
case "Less": |
||||
valid = actualFileSize < Long.parseLong(fileSize); |
||||
break; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid comparator: " + comparator); |
||||
} |
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); |
||||
return null; |
||||
} |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation") |
||||
@Operation( |
||||
summary = "Checks if a PDF is of a certain rotation", |
||||
description = "Input:PDF Output:Boolean Type:SISO") |
||||
public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request) |
||||
throws IOException, InterruptedException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
int rotation = request.getRotation(); |
||||
String comparator = request.getComparator(); |
||||
|
||||
// Load the PDF
|
||||
PDDocument document = PDDocument.load(inputFile.getInputStream()); |
||||
|
||||
// Get the rotation of the first page
|
||||
PDPage firstPage = document.getPage(0); |
||||
int actualRotation = firstPage.getRotation(); |
||||
boolean valid = false; |
||||
// Perform the comparison
|
||||
switch (comparator) { |
||||
case "Greater": |
||||
valid = actualRotation > rotation; |
||||
break; |
||||
case "Equal": |
||||
valid = actualRotation == rotation; |
||||
break; |
||||
case "Less": |
||||
valid = actualRotation < rotation; |
||||
break; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid comparator: " + comparator); |
||||
} |
||||
|
||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile); |
||||
return null; |
||||
} |
||||
} |
||||
|
@ -1,150 +1,150 @@ |
||||
package stirling.software.SPDF.controller.api.misc; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.apache.pdfbox.pdmodel.PDPage; |
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream; |
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle; |
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import io.swagger.v3.oas.annotations.Operation; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest; |
||||
import stirling.software.SPDF.utils.GeneralUtils; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@RequestMapping("/api/v1/misc") |
||||
@Tag(name = "Misc", description = "Miscellaneous APIs") |
||||
public class PageNumbersController { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); |
||||
|
||||
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") |
||||
@Operation( |
||||
summary = "Add page numbers to a PDF document", |
||||
description = |
||||
"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) |
||||
throws IOException { |
||||
MultipartFile file = request.getFileInput(); |
||||
String customMargin = request.getCustomMargin(); |
||||
int position = request.getPosition(); |
||||
int startingNumber = request.getStartingNumber(); |
||||
String pagesToNumber = request.getPagesToNumber(); |
||||
String customText = request.getCustomText(); |
||||
int pageNumber = startingNumber; |
||||
byte[] fileBytes = file.getBytes(); |
||||
PDDocument document = PDDocument.load(fileBytes); |
||||
|
||||
float marginFactor; |
||||
switch (customMargin.toLowerCase()) { |
||||
case "small": |
||||
marginFactor = 0.02f; |
||||
break; |
||||
case "medium": |
||||
marginFactor = 0.035f; |
||||
break; |
||||
case "large": |
||||
marginFactor = 0.05f; |
||||
break; |
||||
case "x-large": |
||||
marginFactor = 0.075f; |
||||
break; |
||||
|
||||
default: |
||||
marginFactor = 0.035f; |
||||
break; |
||||
} |
||||
|
||||
float fontSize = 12.0f; |
||||
PDType1Font font = PDType1Font.HELVETICA; |
||||
if (pagesToNumber == null || pagesToNumber.length() == 0) { |
||||
pagesToNumber = "all"; |
||||
} |
||||
if (customText == null || customText.length() == 0) { |
||||
customText = "{n}"; |
||||
} |
||||
List<Integer> pagesToNumberList = |
||||
GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages()); |
||||
|
||||
for (int i : pagesToNumberList) { |
||||
PDPage page = document.getPage(i); |
||||
PDRectangle pageSize = page.getMediaBox(); |
||||
|
||||
String text = |
||||
customText != null |
||||
? customText |
||||
.replace("{n}", String.valueOf(pageNumber)) |
||||
.replace("{total}", String.valueOf(document.getNumberOfPages())) |
||||
.replace( |
||||
"{filename}", |
||||
file.getOriginalFilename() |
||||
.replaceFirst("[.][^.]+$", "")) |
||||
: String.valueOf(pageNumber); |
||||
|
||||
float x, y; |
||||
|
||||
int xGroup = (position - 1) % 3; |
||||
int yGroup = 2 - (position - 1) / 3; |
||||
|
||||
switch (xGroup) { |
||||
case 0: // left
|
||||
x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth(); |
||||
break; |
||||
case 1: // center
|
||||
x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); |
||||
break; |
||||
default: // right
|
||||
x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth(); |
||||
break; |
||||
} |
||||
|
||||
switch (yGroup) { |
||||
case 0: // bottom
|
||||
y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight(); |
||||
break; |
||||
case 1: // middle
|
||||
y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); |
||||
break; |
||||
default: // top
|
||||
y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight(); |
||||
break; |
||||
} |
||||
|
||||
PDPageContentStream contentStream = |
||||
new PDPageContentStream( |
||||
document, page, PDPageContentStream.AppendMode.APPEND, true); |
||||
contentStream.beginText(); |
||||
contentStream.setFont(font, fontSize); |
||||
contentStream.newLineAtOffset(x, y); |
||||
contentStream.showText(text); |
||||
contentStream.endText(); |
||||
contentStream.close(); |
||||
|
||||
pageNumber++; |
||||
} |
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
document.save(baos); |
||||
document.close(); |
||||
|
||||
return WebResponseUtils.bytesToWebResponse( |
||||
baos.toByteArray(), |
||||
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", |
||||
MediaType.APPLICATION_PDF); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.controller.api.misc; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.apache.pdfbox.pdmodel.PDPage; |
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream; |
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle; |
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import io.swagger.v3.oas.annotations.Operation; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest; |
||||
import stirling.software.SPDF.utils.GeneralUtils; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@RequestMapping("/api/v1/misc") |
||||
@Tag(name = "Misc", description = "Miscellaneous APIs") |
||||
public class PageNumbersController { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class); |
||||
|
||||
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data") |
||||
@Operation( |
||||
summary = "Add page numbers to a PDF document", |
||||
description = |
||||
"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) |
||||
throws IOException { |
||||
MultipartFile file = request.getFileInput(); |
||||
String customMargin = request.getCustomMargin(); |
||||
int position = request.getPosition(); |
||||
int startingNumber = request.getStartingNumber(); |
||||
String pagesToNumber = request.getPagesToNumber(); |
||||
String customText = request.getCustomText(); |
||||
int pageNumber = startingNumber; |
||||
byte[] fileBytes = file.getBytes(); |
||||
PDDocument document = PDDocument.load(fileBytes); |
||||
|
||||
float marginFactor; |
||||
switch (customMargin.toLowerCase()) { |
||||
case "small": |
||||
marginFactor = 0.02f; |
||||
break; |
||||
case "medium": |
||||
marginFactor = 0.035f; |
||||
break; |
||||
case "large": |
||||
marginFactor = 0.05f; |
||||
break; |
||||
case "x-large": |
||||
marginFactor = 0.075f; |
||||
break; |
||||
|
||||
default: |
||||
marginFactor = 0.035f; |
||||
break; |
||||
} |
||||
|
||||
float fontSize = 12.0f; |
||||
PDType1Font font = PDType1Font.HELVETICA; |
||||
if (pagesToNumber == null || pagesToNumber.length() == 0) { |
||||
pagesToNumber = "all"; |
||||
} |
||||
if (customText == null || customText.length() == 0) { |
||||
customText = "{n}"; |
||||
} |
||||
List<Integer> pagesToNumberList = |
||||
GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages()); |
||||
|
||||
for (int i : pagesToNumberList) { |
||||
PDPage page = document.getPage(i); |
||||
PDRectangle pageSize = page.getMediaBox(); |
||||
|
||||
String text = |
||||
customText != null |
||||
? customText |
||||
.replace("{n}", String.valueOf(pageNumber)) |
||||
.replace("{total}", String.valueOf(document.getNumberOfPages())) |
||||
.replace( |
||||
"{filename}", |
||||
file.getOriginalFilename() |
||||
.replaceFirst("[.][^.]+$", "")) |
||||
: String.valueOf(pageNumber); |
||||
|
||||
float x, y; |
||||
|
||||
int xGroup = (position - 1) % 3; |
||||
int yGroup = 2 - (position - 1) / 3; |
||||
|
||||
switch (xGroup) { |
||||
case 0: // left
|
||||
x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth(); |
||||
break; |
||||
case 1: // center
|
||||
x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); |
||||
break; |
||||
default: // right
|
||||
x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth(); |
||||
break; |
||||
} |
||||
|
||||
switch (yGroup) { |
||||
case 0: // bottom
|
||||
y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight(); |
||||
break; |
||||
case 1: // middle
|
||||
y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); |
||||
break; |
||||
default: // top
|
||||
y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight(); |
||||
break; |
||||
} |
||||
|
||||
PDPageContentStream contentStream = |
||||
new PDPageContentStream( |
||||
document, page, PDPageContentStream.AppendMode.APPEND, true); |
||||
contentStream.beginText(); |
||||
contentStream.setFont(font, fontSize); |
||||
contentStream.newLineAtOffset(x, y); |
||||
contentStream.showText(text); |
||||
contentStream.endText(); |
||||
contentStream.close(); |
||||
|
||||
pageNumber++; |
||||
} |
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
document.save(baos); |
||||
document.close(); |
||||
|
||||
return WebResponseUtils.bytesToWebResponse( |
||||
baos.toByteArray(), |
||||
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", |
||||
MediaType.APPLICATION_PDF); |
||||
} |
||||
} |
||||
|
@ -1,133 +1,133 @@ |
||||
package stirling.software.SPDF.controller.api.pipeline; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.InputStream; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipOutputStream; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException; |
||||
import com.fasterxml.jackson.databind.JsonMappingException; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
import stirling.software.SPDF.model.PipelineConfig; |
||||
import stirling.software.SPDF.model.api.HandleDataRequest; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@RequestMapping("/api/v1/pipeline") |
||||
@Tag(name = "Pipeline", description = "Pipeline APIs") |
||||
public class PipelineController { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class); |
||||
|
||||
final String watchedFoldersDir = "./pipeline/watchedFolders/"; |
||||
final String finishedFoldersDir = "./pipeline/finishedFolders/"; |
||||
@Autowired PipelineProcessor processor; |
||||
|
||||
@Autowired ApplicationProperties applicationProperties; |
||||
|
||||
@Autowired private ObjectMapper objectMapper; |
||||
|
||||
@PostMapping("/handleData") |
||||
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request) |
||||
throws JsonMappingException, JsonProcessingException { |
||||
if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) { |
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); |
||||
} |
||||
|
||||
MultipartFile[] files = request.getFileInput(); |
||||
String jsonString = request.getJson(); |
||||
if (files == null) { |
||||
return null; |
||||
} |
||||
PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class); |
||||
logger.info("Received POST request to /handleData with {} files", files.length); |
||||
try { |
||||
List<Resource> inputFiles = processor.generateInputFiles(files); |
||||
if (inputFiles == null || inputFiles.size() == 0) { |
||||
return null; |
||||
} |
||||
List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config); |
||||
if (outputFiles != null && outputFiles.size() == 1) { |
||||
// If there is only one file, return it directly
|
||||
Resource singleFile = outputFiles.get(0); |
||||
InputStream is = singleFile.getInputStream(); |
||||
byte[] bytes = new byte[(int) singleFile.contentLength()]; |
||||
is.read(bytes); |
||||
is.close(); |
||||
|
||||
logger.info("Returning single file response..."); |
||||
return WebResponseUtils.bytesToWebResponse( |
||||
bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM); |
||||
} else if (outputFiles == null) { |
||||
return null; |
||||
} |
||||
|
||||
// Create a ByteArrayOutputStream to hold the zip
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
ZipOutputStream zipOut = new ZipOutputStream(baos); |
||||
|
||||
// A map to keep track of filenames and their counts
|
||||
Map<String, Integer> filenameCount = new HashMap<>(); |
||||
|
||||
// Loop through each file and add it to the zip
|
||||
for (Resource file : outputFiles) { |
||||
String originalFilename = file.getFilename(); |
||||
String filename = originalFilename; |
||||
|
||||
// Check if the filename already exists, and modify it if necessary
|
||||
if (filenameCount.containsKey(originalFilename)) { |
||||
int count = filenameCount.get(originalFilename); |
||||
String baseName = originalFilename.replaceAll("\\.[^.]*$", ""); |
||||
String extension = originalFilename.replaceAll("^.*\\.", ""); |
||||
filename = baseName + "(" + count + ")." + extension; |
||||
filenameCount.put(originalFilename, count + 1); |
||||
} else { |
||||
filenameCount.put(originalFilename, 1); |
||||
} |
||||
|
||||
ZipEntry zipEntry = new ZipEntry(filename); |
||||
zipOut.putNextEntry(zipEntry); |
||||
|
||||
// Read the file into a byte array
|
||||
InputStream is = file.getInputStream(); |
||||
byte[] bytes = new byte[(int) file.contentLength()]; |
||||
is.read(bytes); |
||||
|
||||
// Write the bytes of the file to the zip
|
||||
zipOut.write(bytes, 0, bytes.length); |
||||
zipOut.closeEntry(); |
||||
|
||||
is.close(); |
||||
} |
||||
|
||||
zipOut.close(); |
||||
|
||||
logger.info("Returning zipped file response..."); |
||||
return WebResponseUtils.boasToWebResponse( |
||||
baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM); |
||||
} catch (Exception e) { |
||||
logger.error("Error handling data: ", e); |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
package stirling.software.SPDF.controller.api.pipeline; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.InputStream; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipOutputStream; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException; |
||||
import com.fasterxml.jackson.databind.JsonMappingException; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties; |
||||
import stirling.software.SPDF.model.PipelineConfig; |
||||
import stirling.software.SPDF.model.api.HandleDataRequest; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@RequestMapping("/api/v1/pipeline") |
||||
@Tag(name = "Pipeline", description = "Pipeline APIs") |
||||
public class PipelineController { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class); |
||||
|
||||
final String watchedFoldersDir = "./pipeline/watchedFolders/"; |
||||
final String finishedFoldersDir = "./pipeline/finishedFolders/"; |
||||
@Autowired PipelineProcessor processor; |
||||
|
||||
@Autowired ApplicationProperties applicationProperties; |
||||
|
||||
@Autowired private ObjectMapper objectMapper; |
||||
|
||||
@PostMapping("/handleData") |
||||
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request) |
||||
throws JsonMappingException, JsonProcessingException { |
||||
if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) { |
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); |
||||
} |
||||
|
||||
MultipartFile[] files = request.getFileInput(); |
||||
String jsonString = request.getJson(); |
||||
if (files == null) { |
||||
return null; |
||||
} |
||||
PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class); |
||||
logger.info("Received POST request to /handleData with {} files", files.length); |
||||
try { |
||||
List<Resource> inputFiles = processor.generateInputFiles(files); |
||||
if (inputFiles == null || inputFiles.size() == 0) { |
||||
return null; |
||||
} |
||||
List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config); |
||||
if (outputFiles != null && outputFiles.size() == 1) { |
||||
// If there is only one file, return it directly
|
||||
Resource singleFile = outputFiles.get(0); |
||||
InputStream is = singleFile.getInputStream(); |
||||
byte[] bytes = new byte[(int) singleFile.contentLength()]; |
||||
is.read(bytes); |
||||
is.close(); |
||||
|
||||
logger.info("Returning single file response..."); |
||||
return WebResponseUtils.bytesToWebResponse( |
||||
bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM); |
||||
} else if (outputFiles == null) { |
||||
return null; |
||||
} |
||||
|
||||
// Create a ByteArrayOutputStream to hold the zip
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
ZipOutputStream zipOut = new ZipOutputStream(baos); |
||||
|
||||
// A map to keep track of filenames and their counts
|
||||
Map<String, Integer> filenameCount = new HashMap<>(); |
||||
|
||||
// Loop through each file and add it to the zip
|
||||
for (Resource file : outputFiles) { |
||||
String originalFilename = file.getFilename(); |
||||
String filename = originalFilename; |
||||
|
||||
// Check if the filename already exists, and modify it if necessary
|
||||
if (filenameCount.containsKey(originalFilename)) { |
||||
int count = filenameCount.get(originalFilename); |
||||
String baseName = originalFilename.replaceAll("\\.[^.]*$", ""); |
||||
String extension = originalFilename.replaceAll("^.*\\.", ""); |
||||
filename = baseName + "(" + count + ")." + extension; |
||||
filenameCount.put(originalFilename, count + 1); |
||||
} else { |
||||
filenameCount.put(originalFilename, 1); |
||||
} |
||||
|
||||
ZipEntry zipEntry = new ZipEntry(filename); |
||||
zipOut.putNextEntry(zipEntry); |
||||
|
||||
// Read the file into a byte array
|
||||
InputStream is = file.getInputStream(); |
||||
byte[] bytes = new byte[(int) file.contentLength()]; |
||||
is.read(bytes); |
||||
|
||||
// Write the bytes of the file to the zip
|
||||
zipOut.write(bytes, 0, bytes.length); |
||||
zipOut.closeEntry(); |
||||
|
||||
is.close(); |
||||
} |
||||
|
||||
zipOut.close(); |
||||
|
||||
logger.info("Returning zipped file response..."); |
||||
return WebResponseUtils.boasToWebResponse( |
||||
baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM); |
||||
} catch (Exception e) { |
||||
logger.error("Error handling data: ", e); |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,171 +1,171 @@ |
||||
package stirling.software.SPDF.controller.api.security; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.apache.pdfbox.cos.COSDictionary; |
||||
import org.apache.pdfbox.cos.COSName; |
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog; |
||||
import org.apache.pdfbox.pdmodel.PDPage; |
||||
import org.apache.pdfbox.pdmodel.PDPageTree; |
||||
import org.apache.pdfbox.pdmodel.PDResources; |
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDAction; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions; |
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; |
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; |
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; |
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; |
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import io.swagger.v3.oas.annotations.Operation; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.api.security.SanitizePdfRequest; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@RequestMapping("/api/v1/security") |
||||
@Tag(name = "Security", description = "Security APIs") |
||||
public class SanitizeController { |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") |
||||
@Operation( |
||||
summary = "Sanitize a PDF file", |
||||
description = |
||||
"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) |
||||
throws IOException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
boolean removeJavaScript = request.isRemoveJavaScript(); |
||||
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles(); |
||||
boolean removeMetadata = request.isRemoveMetadata(); |
||||
boolean removeLinks = request.isRemoveLinks(); |
||||
boolean removeFonts = request.isRemoveFonts(); |
||||
|
||||
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { |
||||
if (removeJavaScript) { |
||||
sanitizeJavaScript(document); |
||||
} |
||||
|
||||
if (removeEmbeddedFiles) { |
||||
sanitizeEmbeddedFiles(document); |
||||
} |
||||
|
||||
if (removeMetadata) { |
||||
sanitizeMetadata(document); |
||||
} |
||||
|
||||
if (removeLinks) { |
||||
sanitizeLinks(document); |
||||
} |
||||
|
||||
if (removeFonts) { |
||||
sanitizeFonts(document); |
||||
} |
||||
|
||||
return WebResponseUtils.pdfDocToWebResponse( |
||||
document, |
||||
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") |
||||
+ "_sanitized.pdf"); |
||||
} |
||||
} |
||||
|
||||
private void sanitizeJavaScript(PDDocument document) throws IOException { |
||||
// Get the root dictionary (catalog) of the PDF
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog(); |
||||
|
||||
// Get the Names dictionary
|
||||
COSDictionary namesDict = |
||||
(COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES); |
||||
|
||||
if (namesDict != null) { |
||||
// Get the JavaScript dictionary
|
||||
COSDictionary javaScriptDict = |
||||
(COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript")); |
||||
|
||||
if (javaScriptDict != null) { |
||||
// Remove the JavaScript dictionary
|
||||
namesDict.removeItem(COSName.getPDFName("JavaScript")); |
||||
} |
||||
} |
||||
|
||||
for (PDPage page : document.getPages()) { |
||||
for (PDAnnotation annotation : page.getAnnotations()) { |
||||
if (annotation instanceof PDAnnotationWidget) { |
||||
PDAnnotationWidget widget = (PDAnnotationWidget) annotation; |
||||
PDAction action = widget.getAction(); |
||||
if (action instanceof PDActionJavaScript) { |
||||
widget.setAction(null); |
||||
} |
||||
} |
||||
} |
||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); |
||||
if (acroForm != null) { |
||||
for (PDField field : acroForm.getFields()) { |
||||
PDFormFieldAdditionalActions actions = field.getActions(); |
||||
if (actions != null) { |
||||
if (actions.getC() instanceof PDActionJavaScript) { |
||||
actions.setC(null); |
||||
} |
||||
if (actions.getF() instanceof PDActionJavaScript) { |
||||
actions.setF(null); |
||||
} |
||||
if (actions.getK() instanceof PDActionJavaScript) { |
||||
actions.setK(null); |
||||
} |
||||
if (actions.getV() instanceof PDActionJavaScript) { |
||||
actions.setV(null); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void sanitizeEmbeddedFiles(PDDocument document) { |
||||
PDPageTree allPages = document.getPages(); |
||||
|
||||
for (PDPage page : allPages) { |
||||
PDResources res = page.getResources(); |
||||
|
||||
// Remove embedded files from the PDF
|
||||
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles")); |
||||
} |
||||
} |
||||
|
||||
private void sanitizeMetadata(PDDocument document) { |
||||
PDMetadata metadata = document.getDocumentCatalog().getMetadata(); |
||||
if (metadata != null) { |
||||
document.getDocumentCatalog().setMetadata(null); |
||||
} |
||||
} |
||||
|
||||
private void sanitizeLinks(PDDocument document) throws IOException { |
||||
for (PDPage page : document.getPages()) { |
||||
for (PDAnnotation annotation : page.getAnnotations()) { |
||||
if (annotation instanceof PDAnnotationLink) { |
||||
PDAction action = ((PDAnnotationLink) annotation).getAction(); |
||||
if (action instanceof PDActionLaunch || action instanceof PDActionURI) { |
||||
((PDAnnotationLink) annotation).setAction(null); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void sanitizeFonts(PDDocument document) { |
||||
for (PDPage page : document.getPages()) { |
||||
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font")); |
||||
} |
||||
} |
||||
} |
||||
package stirling.software.SPDF.controller.api.security; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import org.apache.pdfbox.cos.COSDictionary; |
||||
import org.apache.pdfbox.cos.COSName; |
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog; |
||||
import org.apache.pdfbox.pdmodel.PDPage; |
||||
import org.apache.pdfbox.pdmodel.PDPageTree; |
||||
import org.apache.pdfbox.pdmodel.PDResources; |
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDAction; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI; |
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions; |
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; |
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; |
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; |
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; |
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import io.swagger.v3.oas.annotations.Operation; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
import stirling.software.SPDF.model.api.security.SanitizePdfRequest; |
||||
import stirling.software.SPDF.utils.WebResponseUtils; |
||||
|
||||
@RestController |
||||
@RequestMapping("/api/v1/security") |
||||
@Tag(name = "Security", description = "Security APIs") |
||||
public class SanitizeController { |
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf") |
||||
@Operation( |
||||
summary = "Sanitize a PDF file", |
||||
description = |
||||
"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) |
||||
throws IOException { |
||||
MultipartFile inputFile = request.getFileInput(); |
||||
boolean removeJavaScript = request.isRemoveJavaScript(); |
||||
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles(); |
||||
boolean removeMetadata = request.isRemoveMetadata(); |
||||
boolean removeLinks = request.isRemoveLinks(); |
||||
boolean removeFonts = request.isRemoveFonts(); |
||||
|
||||
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) { |
||||
if (removeJavaScript) { |
||||
sanitizeJavaScript(document); |
||||
} |
||||
|
||||
if (removeEmbeddedFiles) { |
||||
sanitizeEmbeddedFiles(document); |
||||
} |
||||
|
||||
if (removeMetadata) { |
||||
sanitizeMetadata(document); |
||||
} |
||||
|
||||
if (removeLinks) { |
||||
sanitizeLinks(document); |
||||
} |
||||
|
||||
if (removeFonts) { |
||||
sanitizeFonts(document); |
||||
} |
||||
|
||||
return WebResponseUtils.pdfDocToWebResponse( |
||||
document, |
||||
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") |
||||
+ "_sanitized.pdf"); |
||||
} |
||||
} |
||||
|
||||
private void sanitizeJavaScript(PDDocument document) throws IOException { |
||||
// Get the root dictionary (catalog) of the PDF
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog(); |
||||
|
||||
// Get the Names dictionary
|
||||
COSDictionary namesDict = |
||||
(COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES); |
||||
|
||||
if (namesDict != null) { |
||||
// Get the JavaScript dictionary
|
||||
COSDictionary javaScriptDict = |
||||
(COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript")); |
||||
|
||||
if (javaScriptDict != null) { |
||||
// Remove the JavaScript dictionary
|
||||
namesDict.removeItem(COSName.getPDFName("JavaScript")); |
||||
} |
||||
} |
||||
|
||||
for (PDPage page : document.getPages()) { |
||||
for (PDAnnotation annotation : page.getAnnotations()) { |
||||
if (annotation instanceof PDAnnotationWidget) { |
||||
PDAnnotationWidget widget = (PDAnnotationWidget) annotation; |
||||
PDAction action = widget.getAction(); |
||||
if (action instanceof PDActionJavaScript) { |
||||
widget.setAction(null); |
||||
} |
||||
} |
||||
} |
||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); |
||||
if (acroForm != null) { |
||||
for (PDField field : acroForm.getFields()) { |
||||
PDFormFieldAdditionalActions actions = field.getActions(); |
||||
if (actions != null) { |
||||
if (actions.getC() instanceof PDActionJavaScript) { |
||||
actions.setC(null); |
||||
} |
||||
if (actions.getF() instanceof PDActionJavaScript) { |
||||
actions.setF(null); |
||||
} |
||||
if (actions.getK() instanceof PDActionJavaScript) { |
||||
actions.setK(null); |
||||
} |
||||
if (actions.getV() instanceof PDActionJavaScript) { |
||||
actions.setV(null); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void sanitizeEmbeddedFiles(PDDocument document) { |
||||
PDPageTree allPages = document.getPages(); |
||||
|
||||
for (PDPage page : allPages) { |
||||
PDResources res = page.getResources(); |
||||
|
||||
// Remove embedded files from the PDF
|
||||
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles")); |
||||
} |
||||
} |
||||
|
||||
private void sanitizeMetadata(PDDocument document) { |
||||
PDMetadata metadata = document.getDocumentCatalog().getMetadata(); |
||||
if (metadata != null) { |
||||
document.getDocumentCatalog().setMetadata(null); |
||||
} |
||||
} |
||||
|
||||
private void sanitizeLinks(PDDocument document) throws IOException { |
||||
for (PDPage page : document.getPages()) { |
||||
for (PDAnnotation annotation : page.getAnnotations()) { |
||||
if (annotation instanceof PDAnnotationLink) { |
||||
PDAction action = ((PDAnnotationLink) annotation).getAction(); |
||||
if (action instanceof PDActionLaunch || action instanceof PDActionURI) { |
||||
((PDAnnotationLink) annotation).setAction(null); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void sanitizeFonts(PDDocument document) { |
||||
for (PDPage page : document.getPages()) { |
||||
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font")); |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,113 +1,113 @@ |
||||
package stirling.software.SPDF.controller.web; |
||||
|
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.ui.Model; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.servlet.ModelAndView; |
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
@Controller |
||||
@Tag(name = "Convert", description = "Convert APIs") |
||||
public class ConverterWebController { |
||||
|
||||
@GetMapping("/img-to-pdf") |
||||
@Hidden |
||||
public String convertImgToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "img-to-pdf"); |
||||
return "convert/img-to-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/html-to-pdf") |
||||
@Hidden |
||||
public String convertHTMLToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "html-to-pdf"); |
||||
return "convert/html-to-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/markdown-to-pdf") |
||||
@Hidden |
||||
public String convertMarkdownToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "markdown-to-pdf"); |
||||
return "convert/markdown-to-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/url-to-pdf") |
||||
@Hidden |
||||
public String convertURLToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "url-to-pdf"); |
||||
return "convert/url-to-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-img") |
||||
@Hidden |
||||
public String pdfToimgForm(Model model) { |
||||
model.addAttribute("currentPage", "pdf-to-img"); |
||||
return "convert/pdf-to-img"; |
||||
} |
||||
|
||||
@GetMapping("/file-to-pdf") |
||||
@Hidden |
||||
public String convertToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "file-to-pdf"); |
||||
return "convert/file-to-pdf"; |
||||
} |
||||
|
||||
// PDF TO......
|
||||
|
||||
@GetMapping("/pdf-to-html") |
||||
@Hidden |
||||
public ModelAndView pdfToHTML() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); |
||||
modelAndView.addObject("currentPage", "pdf-to-html"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-presentation") |
||||
@Hidden |
||||
public ModelAndView pdfToPresentation() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation"); |
||||
modelAndView.addObject("currentPage", "pdf-to-presentation"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-text") |
||||
@Hidden |
||||
public ModelAndView pdfToText() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text"); |
||||
modelAndView.addObject("currentPage", "pdf-to-text"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-word") |
||||
@Hidden |
||||
public ModelAndView pdfToWord() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); |
||||
modelAndView.addObject("currentPage", "pdf-to-word"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-xml") |
||||
@Hidden |
||||
public ModelAndView pdfToXML() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml"); |
||||
modelAndView.addObject("currentPage", "pdf-to-xml"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-csv") |
||||
@Hidden |
||||
public ModelAndView pdfToCSV() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv"); |
||||
modelAndView.addObject("currentPage", "pdf-to-csv"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-pdfa") |
||||
@Hidden |
||||
public String pdfToPdfAForm(Model model) { |
||||
model.addAttribute("currentPage", "pdf-to-pdfa"); |
||||
return "convert/pdf-to-pdfa"; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.controller.web; |
||||
|
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.ui.Model; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.servlet.ModelAndView; |
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
@Controller |
||||
@Tag(name = "Convert", description = "Convert APIs") |
||||
public class ConverterWebController { |
||||
|
||||
@GetMapping("/img-to-pdf") |
||||
@Hidden |
||||
public String convertImgToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "img-to-pdf"); |
||||
return "convert/img-to-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/html-to-pdf") |
||||
@Hidden |
||||
public String convertHTMLToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "html-to-pdf"); |
||||
return "convert/html-to-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/markdown-to-pdf") |
||||
@Hidden |
||||
public String convertMarkdownToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "markdown-to-pdf"); |
||||
return "convert/markdown-to-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/url-to-pdf") |
||||
@Hidden |
||||
public String convertURLToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "url-to-pdf"); |
||||
return "convert/url-to-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-img") |
||||
@Hidden |
||||
public String pdfToimgForm(Model model) { |
||||
model.addAttribute("currentPage", "pdf-to-img"); |
||||
return "convert/pdf-to-img"; |
||||
} |
||||
|
||||
@GetMapping("/file-to-pdf") |
||||
@Hidden |
||||
public String convertToPdfForm(Model model) { |
||||
model.addAttribute("currentPage", "file-to-pdf"); |
||||
return "convert/file-to-pdf"; |
||||
} |
||||
|
||||
// PDF TO......
|
||||
|
||||
@GetMapping("/pdf-to-html") |
||||
@Hidden |
||||
public ModelAndView pdfToHTML() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html"); |
||||
modelAndView.addObject("currentPage", "pdf-to-html"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-presentation") |
||||
@Hidden |
||||
public ModelAndView pdfToPresentation() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation"); |
||||
modelAndView.addObject("currentPage", "pdf-to-presentation"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-text") |
||||
@Hidden |
||||
public ModelAndView pdfToText() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text"); |
||||
modelAndView.addObject("currentPage", "pdf-to-text"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-word") |
||||
@Hidden |
||||
public ModelAndView pdfToWord() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word"); |
||||
modelAndView.addObject("currentPage", "pdf-to-word"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-xml") |
||||
@Hidden |
||||
public ModelAndView pdfToXML() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml"); |
||||
modelAndView.addObject("currentPage", "pdf-to-xml"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-csv") |
||||
@Hidden |
||||
public ModelAndView pdfToCSV() { |
||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv"); |
||||
modelAndView.addObject("currentPage", "pdf-to-csv"); |
||||
return modelAndView; |
||||
} |
||||
|
||||
@GetMapping("/pdf-to-pdfa") |
||||
@Hidden |
||||
public String pdfToPdfAForm(Model model) { |
||||
model.addAttribute("currentPage", "pdf-to-pdfa"); |
||||
return "convert/pdf-to-pdfa"; |
||||
} |
||||
} |
||||
|
@ -1,69 +1,69 @@ |
||||
package stirling.software.SPDF.controller.web; |
||||
|
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.ui.Model; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
@Controller |
||||
@Tag(name = "Security", description = "Security APIs") |
||||
public class SecurityWebController { |
||||
|
||||
@GetMapping("/auto-redact") |
||||
@Hidden |
||||
public String autoRedactForm(Model model) { |
||||
model.addAttribute("currentPage", "auto-redact"); |
||||
return "security/auto-redact"; |
||||
} |
||||
|
||||
@GetMapping("/add-password") |
||||
@Hidden |
||||
public String addPasswordForm(Model model) { |
||||
model.addAttribute("currentPage", "add-password"); |
||||
return "security/add-password"; |
||||
} |
||||
|
||||
@GetMapping("/change-permissions") |
||||
@Hidden |
||||
public String permissionsForm(Model model) { |
||||
model.addAttribute("currentPage", "change-permissions"); |
||||
return "security/change-permissions"; |
||||
} |
||||
|
||||
@GetMapping("/remove-password") |
||||
@Hidden |
||||
public String removePasswordForm(Model model) { |
||||
model.addAttribute("currentPage", "remove-password"); |
||||
return "security/remove-password"; |
||||
} |
||||
|
||||
@GetMapping("/add-watermark") |
||||
@Hidden |
||||
public String addWatermarkForm(Model model) { |
||||
model.addAttribute("currentPage", "add-watermark"); |
||||
return "security/add-watermark"; |
||||
} |
||||
|
||||
@GetMapping("/cert-sign") |
||||
@Hidden |
||||
public String certSignForm(Model model) { |
||||
model.addAttribute("currentPage", "cert-sign"); |
||||
return "security/cert-sign"; |
||||
} |
||||
|
||||
@GetMapping("/sanitize-pdf") |
||||
@Hidden |
||||
public String sanitizeForm(Model model) { |
||||
model.addAttribute("currentPage", "sanitize-pdf"); |
||||
return "security/sanitize-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/get-info-on-pdf") |
||||
@Hidden |
||||
public String getInfo(Model model) { |
||||
model.addAttribute("currentPage", "get-info-on-pdf"); |
||||
return "security/get-info-on-pdf"; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.controller.web; |
||||
|
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.ui.Model; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden; |
||||
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
||||
@Controller |
||||
@Tag(name = "Security", description = "Security APIs") |
||||
public class SecurityWebController { |
||||
|
||||
@GetMapping("/auto-redact") |
||||
@Hidden |
||||
public String autoRedactForm(Model model) { |
||||
model.addAttribute("currentPage", "auto-redact"); |
||||
return "security/auto-redact"; |
||||
} |
||||
|
||||
@GetMapping("/add-password") |
||||
@Hidden |
||||
public String addPasswordForm(Model model) { |
||||
model.addAttribute("currentPage", "add-password"); |
||||
return "security/add-password"; |
||||
} |
||||
|
||||
@GetMapping("/change-permissions") |
||||
@Hidden |
||||
public String permissionsForm(Model model) { |
||||
model.addAttribute("currentPage", "change-permissions"); |
||||
return "security/change-permissions"; |
||||
} |
||||
|
||||
@GetMapping("/remove-password") |
||||
@Hidden |
||||
public String removePasswordForm(Model model) { |
||||
model.addAttribute("currentPage", "remove-password"); |
||||
return "security/remove-password"; |
||||
} |
||||
|
||||
@GetMapping("/add-watermark") |
||||
@Hidden |
||||
public String addWatermarkForm(Model model) { |
||||
model.addAttribute("currentPage", "add-watermark"); |
||||
return "security/add-watermark"; |
||||
} |
||||
|
||||
@GetMapping("/cert-sign") |
||||
@Hidden |
||||
public String certSignForm(Model model) { |
||||
model.addAttribute("currentPage", "cert-sign"); |
||||
return "security/cert-sign"; |
||||
} |
||||
|
||||
@GetMapping("/sanitize-pdf") |
||||
@Hidden |
||||
public String sanitizeForm(Model model) { |
||||
model.addAttribute("currentPage", "sanitize-pdf"); |
||||
return "security/sanitize-pdf"; |
||||
} |
||||
|
||||
@GetMapping("/get-info-on-pdf") |
||||
@Hidden |
||||
public String getInfo(Model model) { |
||||
model.addAttribute("currentPage", "get-info-on-pdf"); |
||||
return "security/get-info-on-pdf"; |
||||
} |
||||
} |
||||
|
@ -1,186 +1,186 @@ |
||||
package stirling.software.SPDF.utils; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.net.MalformedURLException; |
||||
import java.net.URL; |
||||
import java.nio.file.FileVisitResult; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.nio.file.Paths; |
||||
import java.nio.file.SimpleFileVisitor; |
||||
import java.nio.file.attribute.BasicFileAttributes; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
public class GeneralUtils { |
||||
|
||||
public static void deleteDirectory(Path path) throws IOException { |
||||
Files.walkFileTree( |
||||
path, |
||||
new SimpleFileVisitor<Path>() { |
||||
@Override |
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) |
||||
throws IOException { |
||||
Files.delete(file); |
||||
return FileVisitResult.CONTINUE; |
||||
} |
||||
|
||||
@Override |
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) |
||||
throws IOException { |
||||
Files.delete(dir); |
||||
return FileVisitResult.CONTINUE; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public static String convertToFileName(String name) { |
||||
String safeName = name.replaceAll("[^a-zA-Z0-9]", "_"); |
||||
if (safeName.length() > 50) { |
||||
safeName = safeName.substring(0, 50); |
||||
} |
||||
return safeName; |
||||
} |
||||
|
||||
public static boolean isValidURL(String urlStr) { |
||||
try { |
||||
new URL(urlStr); |
||||
return true; |
||||
} catch (MalformedURLException e) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static File multipartToFile(MultipartFile multipart) throws IOException { |
||||
Path tempFile = Files.createTempFile("overlay-", ".pdf"); |
||||
try (InputStream in = multipart.getInputStream(); |
||||
FileOutputStream out = new FileOutputStream(tempFile.toFile())) { |
||||
byte[] buffer = new byte[1024]; |
||||
int bytesRead; |
||||
while ((bytesRead = in.read(buffer)) != -1) { |
||||
out.write(buffer, 0, bytesRead); |
||||
} |
||||
} |
||||
return tempFile.toFile(); |
||||
} |
||||
|
||||
public static Long convertSizeToBytes(String sizeStr) { |
||||
if (sizeStr == null) { |
||||
return null; |
||||
} |
||||
|
||||
sizeStr = sizeStr.trim().toUpperCase(); |
||||
try { |
||||
if (sizeStr.endsWith("KB")) { |
||||
return (long) |
||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); |
||||
} else if (sizeStr.endsWith("MB")) { |
||||
return (long) |
||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) |
||||
* 1024 |
||||
* 1024); |
||||
} else if (sizeStr.endsWith("GB")) { |
||||
return (long) |
||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) |
||||
* 1024 |
||||
* 1024 |
||||
* 1024); |
||||
} else if (sizeStr.endsWith("B")) { |
||||
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); |
||||
} else { |
||||
// Assume MB if no unit is specified
|
||||
return (long) (Double.parseDouble(sizeStr) * 1024 * 1024); |
||||
} |
||||
} catch (NumberFormatException e) { |
||||
// The numeric part of the input string cannot be parsed, handle this case
|
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
public static List<Integer> parsePageString(String pageOrder, int totalPages) { |
||||
return parsePageList(pageOrder.split(","), totalPages); |
||||
} |
||||
|
||||
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) { |
||||
List<Integer> newPageOrder = new ArrayList<>(); |
||||
|
||||
// loop through the page order array
|
||||
for (String element : pageOrderArr) { |
||||
if (element.equalsIgnoreCase("all")) { |
||||
for (int i = 0; i < totalPages; i++) { |
||||
newPageOrder.add(i); |
||||
} |
||||
// As all pages are already added, no need to check further
|
||||
break; |
||||
} else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { |
||||
// Handle page order as a function
|
||||
int coefficient = 0; |
||||
int constant = 0; |
||||
boolean coefficientExists = false; |
||||
boolean constantExists = false; |
||||
|
||||
if (element.contains("n")) { |
||||
String[] parts = element.split("n"); |
||||
if (!parts[0].equals("") && parts[0] != null) { |
||||
coefficient = Integer.parseInt(parts[0]); |
||||
coefficientExists = true; |
||||
} |
||||
if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { |
||||
constant = Integer.parseInt(parts[1]); |
||||
constantExists = true; |
||||
} |
||||
} else if (element.contains("+")) { |
||||
constant = Integer.parseInt(element.replace("+", "")); |
||||
constantExists = true; |
||||
} |
||||
|
||||
for (int i = 1; i <= totalPages; i++) { |
||||
int pageNum = coefficientExists ? coefficient * i : i; |
||||
pageNum += constantExists ? constant : 0; |
||||
|
||||
if (pageNum <= totalPages && pageNum > 0) { |
||||
newPageOrder.add(pageNum - 1); |
||||
} |
||||
} |
||||
} else if (element.contains("-")) { |
||||
// split the range into start and end page
|
||||
String[] range = element.split("-"); |
||||
int start = Integer.parseInt(range[0]); |
||||
int end = Integer.parseInt(range[1]); |
||||
// check if the end page is greater than total pages
|
||||
if (end > totalPages) { |
||||
end = totalPages; |
||||
} |
||||
// loop through the range of pages
|
||||
for (int j = start; j <= end; j++) { |
||||
// print the current index
|
||||
newPageOrder.add(j - 1); |
||||
} |
||||
} else { |
||||
// if the element is a single page
|
||||
newPageOrder.add(Integer.parseInt(element) - 1); |
||||
} |
||||
} |
||||
|
||||
return newPageOrder; |
||||
} |
||||
|
||||
public static boolean createDir(String path) { |
||||
Path folder = Paths.get(path); |
||||
if (!Files.exists(folder)) { |
||||
try { |
||||
Files.createDirectories(folder); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
} |
||||
package stirling.software.SPDF.utils; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.net.MalformedURLException; |
||||
import java.net.URL; |
||||
import java.nio.file.FileVisitResult; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.nio.file.Paths; |
||||
import java.nio.file.SimpleFileVisitor; |
||||
import java.nio.file.attribute.BasicFileAttributes; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
public class GeneralUtils { |
||||
|
||||
public static void deleteDirectory(Path path) throws IOException { |
||||
Files.walkFileTree( |
||||
path, |
||||
new SimpleFileVisitor<Path>() { |
||||
@Override |
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) |
||||
throws IOException { |
||||
Files.delete(file); |
||||
return FileVisitResult.CONTINUE; |
||||
} |
||||
|
||||
@Override |
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) |
||||
throws IOException { |
||||
Files.delete(dir); |
||||
return FileVisitResult.CONTINUE; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public static String convertToFileName(String name) { |
||||
String safeName = name.replaceAll("[^a-zA-Z0-9]", "_"); |
||||
if (safeName.length() > 50) { |
||||
safeName = safeName.substring(0, 50); |
||||
} |
||||
return safeName; |
||||
} |
||||
|
||||
public static boolean isValidURL(String urlStr) { |
||||
try { |
||||
new URL(urlStr); |
||||
return true; |
||||
} catch (MalformedURLException e) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static File multipartToFile(MultipartFile multipart) throws IOException { |
||||
Path tempFile = Files.createTempFile("overlay-", ".pdf"); |
||||
try (InputStream in = multipart.getInputStream(); |
||||
FileOutputStream out = new FileOutputStream(tempFile.toFile())) { |
||||
byte[] buffer = new byte[1024]; |
||||
int bytesRead; |
||||
while ((bytesRead = in.read(buffer)) != -1) { |
||||
out.write(buffer, 0, bytesRead); |
||||
} |
||||
} |
||||
return tempFile.toFile(); |
||||
} |
||||
|
||||
public static Long convertSizeToBytes(String sizeStr) { |
||||
if (sizeStr == null) { |
||||
return null; |
||||
} |
||||
|
||||
sizeStr = sizeStr.trim().toUpperCase(); |
||||
try { |
||||
if (sizeStr.endsWith("KB")) { |
||||
return (long) |
||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024); |
||||
} else if (sizeStr.endsWith("MB")) { |
||||
return (long) |
||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) |
||||
* 1024 |
||||
* 1024); |
||||
} else if (sizeStr.endsWith("GB")) { |
||||
return (long) |
||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) |
||||
* 1024 |
||||
* 1024 |
||||
* 1024); |
||||
} else if (sizeStr.endsWith("B")) { |
||||
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1)); |
||||
} else { |
||||
// Assume MB if no unit is specified
|
||||
return (long) (Double.parseDouble(sizeStr) * 1024 * 1024); |
||||
} |
||||
} catch (NumberFormatException e) { |
||||
// The numeric part of the input string cannot be parsed, handle this case
|
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
public static List<Integer> parsePageString(String pageOrder, int totalPages) { |
||||
return parsePageList(pageOrder.split(","), totalPages); |
||||
} |
||||
|
||||
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) { |
||||
List<Integer> newPageOrder = new ArrayList<>(); |
||||
|
||||
// loop through the page order array
|
||||
for (String element : pageOrderArr) { |
||||
if (element.equalsIgnoreCase("all")) { |
||||
for (int i = 0; i < totalPages; i++) { |
||||
newPageOrder.add(i); |
||||
} |
||||
// As all pages are already added, no need to check further
|
||||
break; |
||||
} else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) { |
||||
// Handle page order as a function
|
||||
int coefficient = 0; |
||||
int constant = 0; |
||||
boolean coefficientExists = false; |
||||
boolean constantExists = false; |
||||
|
||||
if (element.contains("n")) { |
||||
String[] parts = element.split("n"); |
||||
if (!parts[0].equals("") && parts[0] != null) { |
||||
coefficient = Integer.parseInt(parts[0]); |
||||
coefficientExists = true; |
||||
} |
||||
if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) { |
||||
constant = Integer.parseInt(parts[1]); |
||||
constantExists = true; |
||||
} |
||||
} else if (element.contains("+")) { |
||||
constant = Integer.parseInt(element.replace("+", "")); |
||||
constantExists = true; |
||||
} |
||||
|
||||
for (int i = 1; i <= totalPages; i++) { |
||||
int pageNum = coefficientExists ? coefficient * i : i; |
||||
pageNum += constantExists ? constant : 0; |
||||
|
||||
if (pageNum <= totalPages && pageNum > 0) { |
||||
newPageOrder.add(pageNum - 1); |
||||
} |
||||
} |
||||
} else if (element.contains("-")) { |
||||
// split the range into start and end page
|
||||
String[] range = element.split("-"); |
||||
int start = Integer.parseInt(range[0]); |
||||
int end = Integer.parseInt(range[1]); |
||||
// check if the end page is greater than total pages
|
||||
if (end > totalPages) { |
||||
end = totalPages; |
||||
} |
||||
// loop through the range of pages
|
||||
for (int j = start; j <= end; j++) { |
||||
// print the current index
|
||||
newPageOrder.add(j - 1); |
||||
} |
||||
} else { |
||||
// if the element is a single page
|
||||
newPageOrder.add(Integer.parseInt(element) - 1); |
||||
} |
||||
} |
||||
|
||||
return newPageOrder; |
||||
} |
||||
|
||||
public static boolean createDir(String path) { |
||||
Path folder = Paths.get(path); |
||||
if (!Files.exists(folder)) { |
||||
try { |
||||
Files.createDirectories(folder); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
} |
||||
|
@ -1,400 +1,400 @@ |
||||
package stirling.software.SPDF.utils; |
||||
|
||||
import java.awt.Graphics; |
||||
import java.awt.image.BufferedImage; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipOutputStream; |
||||
|
||||
import javax.imageio.IIOImage; |
||||
import javax.imageio.ImageIO; |
||||
import javax.imageio.ImageReader; |
||||
import javax.imageio.ImageWriteParam; |
||||
import javax.imageio.ImageWriter; |
||||
import javax.imageio.stream.ImageOutputStream; |
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.apache.pdfbox.pdmodel.PDPage; |
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream; |
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle; |
||||
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; |
||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; |
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; |
||||
import org.apache.pdfbox.rendering.ImageType; |
||||
import org.apache.pdfbox.rendering.PDFRenderer; |
||||
import org.apache.pdfbox.text.PDFTextStripper; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import stirling.software.SPDF.pdf.ImageFinder; |
||||
|
||||
public class PdfUtils { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); |
||||
|
||||
public static PDRectangle textToPageSize(String size) { |
||||
switch (size.toUpperCase()) { |
||||
case "A0": |
||||
return PDRectangle.A0; |
||||
case "A1": |
||||
return PDRectangle.A1; |
||||
case "A2": |
||||
return PDRectangle.A2; |
||||
case "A3": |
||||
return PDRectangle.A3; |
||||
case "A4": |
||||
return PDRectangle.A4; |
||||
case "A5": |
||||
return PDRectangle.A5; |
||||
case "A6": |
||||
return PDRectangle.A6; |
||||
case "LETTER": |
||||
return PDRectangle.LETTER; |
||||
case "LEGAL": |
||||
return PDRectangle.LEGAL; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid standard page size: " + size); |
||||
} |
||||
} |
||||
|
||||
public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException { |
||||
String[] pageOrderArr = pagesToCheck.split(","); |
||||
List<Integer> pageList = |
||||
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); |
||||
|
||||
for (int pageNumber : pageList) { |
||||
PDPage page = document.getPage(pageNumber); |
||||
if (hasImagesOnPage(page)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase) |
||||
throws IOException { |
||||
String[] pageOrderArr = pageNumbersToCheck.split(","); |
||||
List<Integer> pageList = |
||||
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); |
||||
|
||||
for (int pageNumber : pageList) { |
||||
PDPage page = document.getPage(pageNumber); |
||||
if (hasTextOnPage(page, phrase)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public static boolean hasImagesOnPage(PDPage page) throws IOException { |
||||
ImageFinder imageFinder = new ImageFinder(page); |
||||
imageFinder.processPage(page); |
||||
return imageFinder.hasImages(); |
||||
} |
||||
|
||||
public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException { |
||||
PDFTextStripper textStripper = new PDFTextStripper(); |
||||
PDDocument tempDoc = new PDDocument(); |
||||
tempDoc.addPage(page); |
||||
String pageText = textStripper.getText(tempDoc); |
||||
tempDoc.close(); |
||||
return pageText.contains(phrase); |
||||
} |
||||
|
||||
public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) |
||||
throws IOException { |
||||
PDFTextStripper textStripper = new PDFTextStripper(); |
||||
String pdfText = ""; |
||||
|
||||
if (pagesToCheck == null || pagesToCheck.equals("all")) { |
||||
pdfText = textStripper.getText(pdfDocument); |
||||
} else { |
||||
// remove whitespaces
|
||||
pagesToCheck = pagesToCheck.replaceAll("\\s+", ""); |
||||
|
||||
String[] splitPoints = pagesToCheck.split(","); |
||||
for (String splitPoint : splitPoints) { |
||||
if (splitPoint.contains("-")) { |
||||
// Handle page ranges
|
||||
String[] range = splitPoint.split("-"); |
||||
int startPage = Integer.parseInt(range[0]); |
||||
int endPage = Integer.parseInt(range[1]); |
||||
|
||||
for (int i = startPage; i <= endPage; i++) { |
||||
textStripper.setStartPage(i); |
||||
textStripper.setEndPage(i); |
||||
pdfText += textStripper.getText(pdfDocument); |
||||
} |
||||
} else { |
||||
// Handle individual page
|
||||
int page = Integer.parseInt(splitPoint); |
||||
textStripper.setStartPage(page); |
||||
textStripper.setEndPage(page); |
||||
pdfText += textStripper.getText(pdfDocument); |
||||
} |
||||
} |
||||
} |
||||
|
||||
pdfDocument.close(); |
||||
|
||||
return pdfText.contains(text); |
||||
} |
||||
|
||||
public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) |
||||
throws IOException { |
||||
int actualPageCount = pdfDocument.getNumberOfPages(); |
||||
pdfDocument.close(); |
||||
|
||||
switch (comparator.toLowerCase()) { |
||||
case "greater": |
||||
return actualPageCount > pageCount; |
||||
case "equal": |
||||
return actualPageCount == pageCount; |
||||
case "less": |
||||
return actualPageCount < pageCount; |
||||
default: |
||||
throw new IllegalArgumentException( |
||||
"Invalid comparator. Only 'greater', 'equal', and 'less' are supported."); |
||||
} |
||||
} |
||||
|
||||
public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException { |
||||
PDPage firstPage = pdfDocument.getPage(0); |
||||
PDRectangle mediaBox = firstPage.getMediaBox(); |
||||
|
||||
float actualPageWidth = mediaBox.getWidth(); |
||||
float actualPageHeight = mediaBox.getHeight(); |
||||
|
||||
pdfDocument.close(); |
||||
|
||||
// Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4
|
||||
String[] dimensions = expectedPageSize.split("x"); |
||||
float expectedPageWidth = Float.parseFloat(dimensions[0]); |
||||
float expectedPageHeight = Float.parseFloat(dimensions[1]); |
||||
|
||||
// Checks if the actual page size matches the expected page size
|
||||
return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight; |
||||
} |
||||
|
||||
public static byte[] convertFromPdf( |
||||
byte[] inputStream, |
||||
String imageType, |
||||
ImageType colorType, |
||||
boolean singleImage, |
||||
int DPI, |
||||
String filename) |
||||
throws IOException, Exception { |
||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { |
||||
PDFRenderer pdfRenderer = new PDFRenderer(document); |
||||
int pageCount = document.getNumberOfPages(); |
||||
|
||||
// Create a ByteArrayOutputStream to save the image(s) to
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
|
||||
if (singleImage) { |
||||
if (imageType.toLowerCase().equals("tiff") |
||||
|| imageType.toLowerCase().equals("tif")) { |
||||
// Write the images to the output stream as a TIFF with multiple frames
|
||||
ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next(); |
||||
ImageWriteParam param = writer.getDefaultWriteParam(); |
||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); |
||||
param.setCompressionType("ZLib"); |
||||
param.setCompressionQuality(1.0f); |
||||
|
||||
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) { |
||||
writer.setOutput(ios); |
||||
writer.prepareWriteSequence(null); |
||||
|
||||
for (int i = 0; i < pageCount; ++i) { |
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); |
||||
writer.writeToSequence(new IIOImage(image, null, null), param); |
||||
} |
||||
|
||||
writer.endWriteSequence(); |
||||
} |
||||
|
||||
writer.dispose(); |
||||
} else { |
||||
// Combine all images into a single big image
|
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType); |
||||
BufferedImage combined = |
||||
new BufferedImage( |
||||
image.getWidth(), |
||||
image.getHeight() * pageCount, |
||||
BufferedImage.TYPE_INT_RGB); |
||||
Graphics g = combined.getGraphics(); |
||||
|
||||
for (int i = 0; i < pageCount; ++i) { |
||||
if (i != 0) { |
||||
image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); |
||||
} |
||||
g.drawImage(image, 0, i * image.getHeight(), null); |
||||
} |
||||
|
||||
// Write the image to the output stream
|
||||
ImageIO.write(combined, imageType, baos); |
||||
} |
||||
|
||||
// Log that the image was successfully written to the byte array
|
||||
logger.info("Image successfully written to byte array"); |
||||
} else { |
||||
// Zip the images and return as byte array
|
||||
try (ZipOutputStream zos = new ZipOutputStream(baos)) { |
||||
for (int i = 0; i < pageCount; ++i) { |
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); |
||||
try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) { |
||||
ImageIO.write(image, imageType, baosImage); |
||||
|
||||
// Add the image to the zip file
|
||||
zos.putNextEntry( |
||||
new ZipEntry( |
||||
String.format( |
||||
filename + "_%d.%s", |
||||
i + 1, |
||||
imageType.toLowerCase()))); |
||||
zos.write(baosImage.toByteArray()); |
||||
} |
||||
} |
||||
// Log that the images were successfully written to the byte array
|
||||
logger.info("Images successfully written to byte array as a zip"); |
||||
} |
||||
} |
||||
return baos.toByteArray(); |
||||
} catch (IOException e) { |
||||
// Log an error message if there is an issue converting the PDF to an image
|
||||
logger.error("Error converting PDF to image", e); |
||||
throw e; |
||||
} |
||||
} |
||||
|
||||
public static byte[] imageToPdf( |
||||
MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) |
||||
throws IOException { |
||||
try (PDDocument doc = new PDDocument()) { |
||||
for (MultipartFile file : files) { |
||||
String contentType = file.getContentType(); |
||||
String originalFilename = file.getOriginalFilename(); |
||||
if (originalFilename != null |
||||
&& (originalFilename.toLowerCase().endsWith(".tiff") |
||||
|| originalFilename.toLowerCase().endsWith(".tif"))) { |
||||
ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next(); |
||||
reader.setInput(ImageIO.createImageInputStream(file.getInputStream())); |
||||
int numPages = reader.getNumImages(true); |
||||
for (int i = 0; i < numPages; i++) { |
||||
BufferedImage pageImage = reader.read(i); |
||||
BufferedImage convertedImage = |
||||
ImageProcessingUtils.convertColorType(pageImage, colorType); |
||||
PDImageXObject pdImage = |
||||
LosslessFactory.createFromImage(doc, convertedImage); |
||||
addImageToDocument(doc, pdImage, fitOption, autoRotate); |
||||
} |
||||
} else { |
||||
BufferedImage image = ImageIO.read(file.getInputStream()); |
||||
BufferedImage convertedImage = |
||||
ImageProcessingUtils.convertColorType(image, colorType); |
||||
// Use JPEGFactory if it's JPEG since JPEG is lossy
|
||||
PDImageXObject pdImage = |
||||
(contentType != null && contentType.equals("image/jpeg")) |
||||
? JPEGFactory.createFromImage(doc, convertedImage) |
||||
: LosslessFactory.createFromImage(doc, convertedImage); |
||||
addImageToDocument(doc, pdImage, fitOption, autoRotate); |
||||
} |
||||
} |
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); |
||||
doc.save(byteArrayOutputStream); |
||||
logger.info("PDF successfully saved to byte array"); |
||||
return byteArrayOutputStream.toByteArray(); |
||||
} |
||||
} |
||||
|
||||
private static void addImageToDocument( |
||||
PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate) |
||||
throws IOException { |
||||
boolean imageIsLandscape = image.getWidth() > image.getHeight(); |
||||
PDRectangle pageSize = PDRectangle.A4; |
||||
|
||||
System.out.println(fitOption); |
||||
|
||||
if (autoRotate && imageIsLandscape) { |
||||
pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth()); |
||||
} |
||||
|
||||
if ("fitDocumentToImage".equals(fitOption)) { |
||||
pageSize = new PDRectangle(image.getWidth(), image.getHeight()); |
||||
} |
||||
|
||||
PDPage page = new PDPage(pageSize); |
||||
doc.addPage(page); |
||||
|
||||
float pageWidth = page.getMediaBox().getWidth(); |
||||
float pageHeight = page.getMediaBox().getHeight(); |
||||
|
||||
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) { |
||||
if ("fillPage".equals(fitOption) || "fitDocumentToImage".equals(fitOption)) { |
||||
contentStream.drawImage(image, 0, 0, pageWidth, pageHeight); |
||||
} else if ("maintainAspectRatio".equals(fitOption)) { |
||||
float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight(); |
||||
float pageAspectRatio = pageWidth / pageHeight; |
||||
|
||||
float scaleFactor = 1.0f; |
||||
if (imageAspectRatio > pageAspectRatio) { |
||||
scaleFactor = pageWidth / image.getWidth(); |
||||
} else { |
||||
scaleFactor = pageHeight / image.getHeight(); |
||||
} |
||||
|
||||
float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2; |
||||
float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2; |
||||
contentStream.drawImage( |
||||
image, |
||||
xPos, |
||||
yPos, |
||||
image.getWidth() * scaleFactor, |
||||
image.getHeight() * scaleFactor); |
||||
} |
||||
} catch (IOException e) { |
||||
logger.error("Error adding image to PDF", e); |
||||
throw e; |
||||
} |
||||
} |
||||
|
||||
public static byte[] overlayImage( |
||||
byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) |
||||
throws IOException { |
||||
|
||||
PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes)); |
||||
|
||||
// Get the first page of the PDF
|
||||
int pages = document.getNumberOfPages(); |
||||
for (int i = 0; i < pages; i++) { |
||||
PDPage page = document.getPage(i); |
||||
try (PDPageContentStream contentStream = |
||||
new PDPageContentStream( |
||||
document, page, PDPageContentStream.AppendMode.APPEND, true)) { |
||||
// Create an image object from the image bytes
|
||||
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); |
||||
// Draw the image onto the page at the specified x and y coordinates
|
||||
contentStream.drawImage(image, x, y); |
||||
logger.info("Image successfully overlayed onto PDF"); |
||||
if (!everyPage && i == 0) { |
||||
break; |
||||
} |
||||
} catch (IOException e) { |
||||
// Log an error message if there is an issue overlaying the image onto the PDF
|
||||
logger.error("Error overlaying image onto PDF", e); |
||||
throw e; |
||||
} |
||||
} |
||||
// Create a ByteArrayOutputStream to save the PDF to
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
document.save(baos); |
||||
logger.info("PDF successfully saved to byte array"); |
||||
return baos.toByteArray(); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.utils; |
||||
|
||||
import java.awt.Graphics; |
||||
import java.awt.image.BufferedImage; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
import java.util.zip.ZipEntry; |
||||
import java.util.zip.ZipOutputStream; |
||||
|
||||
import javax.imageio.IIOImage; |
||||
import javax.imageio.ImageIO; |
||||
import javax.imageio.ImageReader; |
||||
import javax.imageio.ImageWriteParam; |
||||
import javax.imageio.ImageWriter; |
||||
import javax.imageio.stream.ImageOutputStream; |
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.apache.pdfbox.pdmodel.PDPage; |
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream; |
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle; |
||||
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; |
||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; |
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; |
||||
import org.apache.pdfbox.rendering.ImageType; |
||||
import org.apache.pdfbox.rendering.PDFRenderer; |
||||
import org.apache.pdfbox.text.PDFTextStripper; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import stirling.software.SPDF.pdf.ImageFinder; |
||||
|
||||
public class PdfUtils { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); |
||||
|
||||
public static PDRectangle textToPageSize(String size) { |
||||
switch (size.toUpperCase()) { |
||||
case "A0": |
||||
return PDRectangle.A0; |
||||
case "A1": |
||||
return PDRectangle.A1; |
||||
case "A2": |
||||
return PDRectangle.A2; |
||||
case "A3": |
||||
return PDRectangle.A3; |
||||
case "A4": |
||||
return PDRectangle.A4; |
||||
case "A5": |
||||
return PDRectangle.A5; |
||||
case "A6": |
||||
return PDRectangle.A6; |
||||
case "LETTER": |
||||
return PDRectangle.LETTER; |
||||
case "LEGAL": |
||||
return PDRectangle.LEGAL; |
||||
default: |
||||
throw new IllegalArgumentException("Invalid standard page size: " + size); |
||||
} |
||||
} |
||||
|
||||
public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException { |
||||
String[] pageOrderArr = pagesToCheck.split(","); |
||||
List<Integer> pageList = |
||||
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); |
||||
|
||||
for (int pageNumber : pageList) { |
||||
PDPage page = document.getPage(pageNumber); |
||||
if (hasImagesOnPage(page)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase) |
||||
throws IOException { |
||||
String[] pageOrderArr = pageNumbersToCheck.split(","); |
||||
List<Integer> pageList = |
||||
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages()); |
||||
|
||||
for (int pageNumber : pageList) { |
||||
PDPage page = document.getPage(pageNumber); |
||||
if (hasTextOnPage(page, phrase)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public static boolean hasImagesOnPage(PDPage page) throws IOException { |
||||
ImageFinder imageFinder = new ImageFinder(page); |
||||
imageFinder.processPage(page); |
||||
return imageFinder.hasImages(); |
||||
} |
||||
|
||||
public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException { |
||||
PDFTextStripper textStripper = new PDFTextStripper(); |
||||
PDDocument tempDoc = new PDDocument(); |
||||
tempDoc.addPage(page); |
||||
String pageText = textStripper.getText(tempDoc); |
||||
tempDoc.close(); |
||||
return pageText.contains(phrase); |
||||
} |
||||
|
||||
public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck) |
||||
throws IOException { |
||||
PDFTextStripper textStripper = new PDFTextStripper(); |
||||
String pdfText = ""; |
||||
|
||||
if (pagesToCheck == null || pagesToCheck.equals("all")) { |
||||
pdfText = textStripper.getText(pdfDocument); |
||||
} else { |
||||
// remove whitespaces
|
||||
pagesToCheck = pagesToCheck.replaceAll("\\s+", ""); |
||||
|
||||
String[] splitPoints = pagesToCheck.split(","); |
||||
for (String splitPoint : splitPoints) { |
||||
if (splitPoint.contains("-")) { |
||||
// Handle page ranges
|
||||
String[] range = splitPoint.split("-"); |
||||
int startPage = Integer.parseInt(range[0]); |
||||
int endPage = Integer.parseInt(range[1]); |
||||
|
||||
for (int i = startPage; i <= endPage; i++) { |
||||
textStripper.setStartPage(i); |
||||
textStripper.setEndPage(i); |
||||
pdfText += textStripper.getText(pdfDocument); |
||||
} |
||||
} else { |
||||
// Handle individual page
|
||||
int page = Integer.parseInt(splitPoint); |
||||
textStripper.setStartPage(page); |
||||
textStripper.setEndPage(page); |
||||
pdfText += textStripper.getText(pdfDocument); |
||||
} |
||||
} |
||||
} |
||||
|
||||
pdfDocument.close(); |
||||
|
||||
return pdfText.contains(text); |
||||
} |
||||
|
||||
public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator) |
||||
throws IOException { |
||||
int actualPageCount = pdfDocument.getNumberOfPages(); |
||||
pdfDocument.close(); |
||||
|
||||
switch (comparator.toLowerCase()) { |
||||
case "greater": |
||||
return actualPageCount > pageCount; |
||||
case "equal": |
||||
return actualPageCount == pageCount; |
||||
case "less": |
||||
return actualPageCount < pageCount; |
||||
default: |
||||
throw new IllegalArgumentException( |
||||
"Invalid comparator. Only 'greater', 'equal', and 'less' are supported."); |
||||
} |
||||
} |
||||
|
||||
public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException { |
||||
PDPage firstPage = pdfDocument.getPage(0); |
||||
PDRectangle mediaBox = firstPage.getMediaBox(); |
||||
|
||||
float actualPageWidth = mediaBox.getWidth(); |
||||
float actualPageHeight = mediaBox.getHeight(); |
||||
|
||||
pdfDocument.close(); |
||||
|
||||
// Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4
|
||||
String[] dimensions = expectedPageSize.split("x"); |
||||
float expectedPageWidth = Float.parseFloat(dimensions[0]); |
||||
float expectedPageHeight = Float.parseFloat(dimensions[1]); |
||||
|
||||
// Checks if the actual page size matches the expected page size
|
||||
return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight; |
||||
} |
||||
|
||||
public static byte[] convertFromPdf( |
||||
byte[] inputStream, |
||||
String imageType, |
||||
ImageType colorType, |
||||
boolean singleImage, |
||||
int DPI, |
||||
String filename) |
||||
throws IOException, Exception { |
||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) { |
||||
PDFRenderer pdfRenderer = new PDFRenderer(document); |
||||
int pageCount = document.getNumberOfPages(); |
||||
|
||||
// Create a ByteArrayOutputStream to save the image(s) to
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
|
||||
if (singleImage) { |
||||
if (imageType.toLowerCase().equals("tiff") |
||||
|| imageType.toLowerCase().equals("tif")) { |
||||
// Write the images to the output stream as a TIFF with multiple frames
|
||||
ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next(); |
||||
ImageWriteParam param = writer.getDefaultWriteParam(); |
||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); |
||||
param.setCompressionType("ZLib"); |
||||
param.setCompressionQuality(1.0f); |
||||
|
||||
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) { |
||||
writer.setOutput(ios); |
||||
writer.prepareWriteSequence(null); |
||||
|
||||
for (int i = 0; i < pageCount; ++i) { |
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); |
||||
writer.writeToSequence(new IIOImage(image, null, null), param); |
||||
} |
||||
|
||||
writer.endWriteSequence(); |
||||
} |
||||
|
||||
writer.dispose(); |
||||
} else { |
||||
// Combine all images into a single big image
|
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType); |
||||
BufferedImage combined = |
||||
new BufferedImage( |
||||
image.getWidth(), |
||||
image.getHeight() * pageCount, |
||||
BufferedImage.TYPE_INT_RGB); |
||||
Graphics g = combined.getGraphics(); |
||||
|
||||
for (int i = 0; i < pageCount; ++i) { |
||||
if (i != 0) { |
||||
image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); |
||||
} |
||||
g.drawImage(image, 0, i * image.getHeight(), null); |
||||
} |
||||
|
||||
// Write the image to the output stream
|
||||
ImageIO.write(combined, imageType, baos); |
||||
} |
||||
|
||||
// Log that the image was successfully written to the byte array
|
||||
logger.info("Image successfully written to byte array"); |
||||
} else { |
||||
// Zip the images and return as byte array
|
||||
try (ZipOutputStream zos = new ZipOutputStream(baos)) { |
||||
for (int i = 0; i < pageCount; ++i) { |
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType); |
||||
try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) { |
||||
ImageIO.write(image, imageType, baosImage); |
||||
|
||||
// Add the image to the zip file
|
||||
zos.putNextEntry( |
||||
new ZipEntry( |
||||
String.format( |
||||
filename + "_%d.%s", |
||||
i + 1, |
||||
imageType.toLowerCase()))); |
||||
zos.write(baosImage.toByteArray()); |
||||
} |
||||
} |
||||
// Log that the images were successfully written to the byte array
|
||||
logger.info("Images successfully written to byte array as a zip"); |
||||
} |
||||
} |
||||
return baos.toByteArray(); |
||||
} catch (IOException e) { |
||||
// Log an error message if there is an issue converting the PDF to an image
|
||||
logger.error("Error converting PDF to image", e); |
||||
throw e; |
||||
} |
||||
} |
||||
|
||||
public static byte[] imageToPdf( |
||||
MultipartFile[] files, String fitOption, boolean autoRotate, String colorType) |
||||
throws IOException { |
||||
try (PDDocument doc = new PDDocument()) { |
||||
for (MultipartFile file : files) { |
||||
String contentType = file.getContentType(); |
||||
String originalFilename = file.getOriginalFilename(); |
||||
if (originalFilename != null |
||||
&& (originalFilename.toLowerCase().endsWith(".tiff") |
||||
|| originalFilename.toLowerCase().endsWith(".tif"))) { |
||||
ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next(); |
||||
reader.setInput(ImageIO.createImageInputStream(file.getInputStream())); |
||||
int numPages = reader.getNumImages(true); |
||||
for (int i = 0; i < numPages; i++) { |
||||
BufferedImage pageImage = reader.read(i); |
||||
BufferedImage convertedImage = |
||||
ImageProcessingUtils.convertColorType(pageImage, colorType); |
||||
PDImageXObject pdImage = |
||||
LosslessFactory.createFromImage(doc, convertedImage); |
||||
addImageToDocument(doc, pdImage, fitOption, autoRotate); |
||||
} |
||||
} else { |
||||
BufferedImage image = ImageIO.read(file.getInputStream()); |
||||
BufferedImage convertedImage = |
||||
ImageProcessingUtils.convertColorType(image, colorType); |
||||
// Use JPEGFactory if it's JPEG since JPEG is lossy
|
||||
PDImageXObject pdImage = |
||||
(contentType != null && contentType.equals("image/jpeg")) |
||||
? JPEGFactory.createFromImage(doc, convertedImage) |
||||
: LosslessFactory.createFromImage(doc, convertedImage); |
||||
addImageToDocument(doc, pdImage, fitOption, autoRotate); |
||||
} |
||||
} |
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); |
||||
doc.save(byteArrayOutputStream); |
||||
logger.info("PDF successfully saved to byte array"); |
||||
return byteArrayOutputStream.toByteArray(); |
||||
} |
||||
} |
||||
|
||||
private static void addImageToDocument( |
||||
PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate) |
||||
throws IOException { |
||||
boolean imageIsLandscape = image.getWidth() > image.getHeight(); |
||||
PDRectangle pageSize = PDRectangle.A4; |
||||
|
||||
System.out.println(fitOption); |
||||
|
||||
if (autoRotate && imageIsLandscape) { |
||||
pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth()); |
||||
} |
||||
|
||||
if ("fitDocumentToImage".equals(fitOption)) { |
||||
pageSize = new PDRectangle(image.getWidth(), image.getHeight()); |
||||
} |
||||
|
||||
PDPage page = new PDPage(pageSize); |
||||
doc.addPage(page); |
||||
|
||||
float pageWidth = page.getMediaBox().getWidth(); |
||||
float pageHeight = page.getMediaBox().getHeight(); |
||||
|
||||
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) { |
||||
if ("fillPage".equals(fitOption) || "fitDocumentToImage".equals(fitOption)) { |
||||
contentStream.drawImage(image, 0, 0, pageWidth, pageHeight); |
||||
} else if ("maintainAspectRatio".equals(fitOption)) { |
||||
float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight(); |
||||
float pageAspectRatio = pageWidth / pageHeight; |
||||
|
||||
float scaleFactor = 1.0f; |
||||
if (imageAspectRatio > pageAspectRatio) { |
||||
scaleFactor = pageWidth / image.getWidth(); |
||||
} else { |
||||
scaleFactor = pageHeight / image.getHeight(); |
||||
} |
||||
|
||||
float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2; |
||||
float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2; |
||||
contentStream.drawImage( |
||||
image, |
||||
xPos, |
||||
yPos, |
||||
image.getWidth() * scaleFactor, |
||||
image.getHeight() * scaleFactor); |
||||
} |
||||
} catch (IOException e) { |
||||
logger.error("Error adding image to PDF", e); |
||||
throw e; |
||||
} |
||||
} |
||||
|
||||
public static byte[] overlayImage( |
||||
byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage) |
||||
throws IOException { |
||||
|
||||
PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes)); |
||||
|
||||
// Get the first page of the PDF
|
||||
int pages = document.getNumberOfPages(); |
||||
for (int i = 0; i < pages; i++) { |
||||
PDPage page = document.getPage(i); |
||||
try (PDPageContentStream contentStream = |
||||
new PDPageContentStream( |
||||
document, page, PDPageContentStream.AppendMode.APPEND, true)) { |
||||
// Create an image object from the image bytes
|
||||
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, ""); |
||||
// Draw the image onto the page at the specified x and y coordinates
|
||||
contentStream.drawImage(image, x, y); |
||||
logger.info("Image successfully overlayed onto PDF"); |
||||
if (!everyPage && i == 0) { |
||||
break; |
||||
} |
||||
} catch (IOException e) { |
||||
// Log an error message if there is an issue overlaying the image onto the PDF
|
||||
logger.error("Error overlaying image onto PDF", e); |
||||
throw e; |
||||
} |
||||
} |
||||
// Create a ByteArrayOutputStream to save the PDF to
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
document.save(baos); |
||||
logger.info("PDF successfully saved to byte array"); |
||||
return baos.toByteArray(); |
||||
} |
||||
} |
||||
|
@ -1,67 +1,67 @@ |
||||
package stirling.software.SPDF.utils; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.net.URLEncoder; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
public class WebResponseUtils { |
||||
|
||||
public static ResponseEntity<byte[]> boasToWebResponse( |
||||
ByteArrayOutputStream baos, String docName) throws IOException { |
||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> boasToWebResponse( |
||||
ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException { |
||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> multiPartFileToWebResponse(MultipartFile file) |
||||
throws IOException { |
||||
String fileName = file.getOriginalFilename(); |
||||
MediaType mediaType = MediaType.parseMediaType(file.getContentType()); |
||||
|
||||
byte[] bytes = file.getBytes(); |
||||
|
||||
return bytesToWebResponse(bytes, fileName, mediaType); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> bytesToWebResponse( |
||||
byte[] bytes, String docName, MediaType mediaType) throws IOException { |
||||
|
||||
// Return the PDF as a response
|
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.setContentType(mediaType); |
||||
headers.setContentLength(bytes.length); |
||||
String encodedDocName = |
||||
URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()) |
||||
.replaceAll("\\+", "%20"); |
||||
headers.setContentDispositionFormData("attachment", encodedDocName); |
||||
return new ResponseEntity<>(bytes, headers, HttpStatus.OK); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) |
||||
throws IOException { |
||||
return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) |
||||
throws IOException { |
||||
|
||||
// Open Byte Array and save document to it
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
document.save(baos); |
||||
// Close the document
|
||||
document.close(); |
||||
|
||||
return boasToWebResponse(baos, docName); |
||||
} |
||||
} |
||||
package stirling.software.SPDF.utils; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.net.URLEncoder; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
public class WebResponseUtils { |
||||
|
||||
public static ResponseEntity<byte[]> boasToWebResponse( |
||||
ByteArrayOutputStream baos, String docName) throws IOException { |
||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> boasToWebResponse( |
||||
ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException { |
||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> multiPartFileToWebResponse(MultipartFile file) |
||||
throws IOException { |
||||
String fileName = file.getOriginalFilename(); |
||||
MediaType mediaType = MediaType.parseMediaType(file.getContentType()); |
||||
|
||||
byte[] bytes = file.getBytes(); |
||||
|
||||
return bytesToWebResponse(bytes, fileName, mediaType); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> bytesToWebResponse( |
||||
byte[] bytes, String docName, MediaType mediaType) throws IOException { |
||||
|
||||
// Return the PDF as a response
|
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.setContentType(mediaType); |
||||
headers.setContentLength(bytes.length); |
||||
String encodedDocName = |
||||
URLEncoder.encode(docName, StandardCharsets.UTF_8.toString()) |
||||
.replaceAll("\\+", "%20"); |
||||
headers.setContentDispositionFormData("attachment", encodedDocName); |
||||
return new ResponseEntity<>(bytes, headers, HttpStatus.OK); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName) |
||||
throws IOException { |
||||
return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF); |
||||
} |
||||
|
||||
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName) |
||||
throws IOException { |
||||
|
||||
// Open Byte Array and save document to it
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
document.save(baos); |
||||
// Close the document
|
||||
document.close(); |
||||
|
||||
return boasToWebResponse(baos, docName); |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue