1
0
mirror of https://github.com/Stirling-Tools/Stirling-PDF.git synced 2024-09-21 04:10:38 +02:00

Compare commits

...

37 Commits

Author SHA1 Message Date
Anthony Stirling
6606850e4a fix version number 2024-06-06 22:38:22 +01:00
Anthony Stirling
7b08d98232
Merge pull request #1394 from Stirling-Tools/disableConfigUpdater
Disable config updater
2024-06-06 22:05:53 +01:00
Anthony Stirling
03150c6462 format 2024-06-06 21:59:13 +01:00
a
a3bf7baf35 Merge branch 'disableConfigUpdater' of git@github.com:Stirling-Tools/Stirling-PDF.git into disableConfigUpdater 2024-06-06 21:57:30 +01:00
Anthony Stirling
6c09bcf23c resolve #1386 2024-06-06 21:57:18 +01:00
Anthony Stirling
e11fa01d10
Merge pull request #1393 from Stirling-Tools/disableConfigUpdater
resolve admin config with custom path
2024-06-06 21:43:15 +01:00
Anthony Stirling
d60107f48b
Merge branch 'main' into disableConfigUpdater 2024-06-06 21:36:04 +01:00
Anthony Stirling
0b449af9ba resolve path 2024-06-06 21:34:56 +01:00
Anthony Stirling
4c9c0207ba fancy button 2024-06-06 21:27:58 +01:00
Anthony Stirling
d36a59442f th action 2024-06-06 21:24:41 +01:00
Anthony Stirling
fd4c75279f init 2024-06-06 21:23:33 +01:00
Anthony Stirling
fd5f5025ce
Merge pull request #1391 from Stirling-Tools/sync_version
💾 Update Version
2024-06-06 20:34:52 +01:00
GitHub Action action@github.com
319ecbcbc1 💾 Sync Versions
> Made via sync_files.yml
2024-06-06 19:23:20 +00:00
Anthony Stirling
04b0bcde61
Merge pull request #1388 from Stirling-Tools/disableConfigUpdater
remove settings files update for now
2024-06-06 20:23:04 +01:00
Anthony Stirling
6499b759d9
Merge pull request #1390 from Ludy87/replace_hardcoded
Enhance OAuth2 Client Registration with Dynamic Provider Details
2024-06-06 20:22:40 +01:00
Anthony Stirling
2081c4872d
Merge pull request #1389 from Ludy87/fix_langues_cz_de_nb
Minor corrections in the languages
2024-06-06 20:21:05 +01:00
Ludy87
37c75971f2
Update ApplicationProperties.java 2024-06-06 21:14:34 +02:00
Ludy87
7d9edfca6d
Enhance OAuth2 Client Registration with Dynamic Provider Details 2024-06-06 21:03:06 +02:00
Anthony Stirling
a40696f16e remove settings files update for now 2024-06-06 19:49:53 +01:00
Ludy87
515b5b1492
Minor corrections in the languages 2024-06-06 20:42:50 +02:00
Anthony Stirling
e824a3e7bd
Update build.gradle 2024-06-05 23:18:52 +01:00
Anthony Stirling
9fd508fcc7
Update README.md 2024-06-05 23:17:38 +01:00
Anthony Stirling
a0227a4bdd
Merge pull request #1382 from Stirling-Tools/tweaks
tweaks and fix for #1381
2024-06-05 22:35:22 +01:00
Anthony Stirling
87be41117f tweaks and fix for #1381 2024-06-05 21:16:22 +01:00
Anthony Stirling
3e9123fcd5
Merge pull request #1376 from arsvendg/main
Small adjustmens to language file
2024-06-05 18:28:48 +01:00
arsvendg
7e7c6a3832 Small adjustmens to language file 2024-06-05 13:21:17 +02:00
Anthony Stirling
cce31ee0f6
Merge pull request #1373 from arsvendg/main
Norwegian translation
2024-06-05 08:40:19 +01:00
arsvendg
746f341d6a
Merge branch 'Stirling-Tools:main' into main 2024-06-05 09:07:11 +02:00
Anthony Stirling
f35bf120e9
Update flatten.html 2024-06-04 20:17:27 +01:00
arsvendg
1fd4ce339f
Update languages.html 2024-06-04 00:05:34 +02:00
arsvendg
af736ca33a
Add files via upload 2024-06-04 00:03:56 +02:00
arsvendg
0878dd10b8
Add files via upload 2024-06-04 00:03:23 +02:00
Anthony Stirling
948ddb06bc
Merge pull request #1362 from Stirling-Tools/sonar
Sonar
2024-06-02 12:12:41 +01:00
Anthony Stirling
efc07522ab minor 2024-06-02 12:04:29 +01:00
Anthony Stirling
31938b662c format 2024-06-02 12:02:01 +01:00
Anthony Stirling
eb526a5d0c logging and try catch 2024-06-02 11:59:43 +01:00
Anthony Stirling
c4a620e3f5 init sonar 2024-06-02 11:42:30 +01:00
53 changed files with 1491 additions and 233 deletions

View File

@ -159,7 +159,7 @@ Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR
## Supported Languages ## Supported Languages
Stirling PDF currently supports 28! Stirling PDF currently supports 32!
| Language | Progress | | Language | Progress |
| ------------------------------------------- | -------------------------------------- | | ------------------------------------------- | -------------------------------------- |
@ -167,15 +167,15 @@ Stirling PDF currently supports 28!
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| Arabic (العربية) (ar_AR) | ![40%](https://geps.dev/progress/40) | | Arabic (العربية) (ar_AR) | ![40%](https://geps.dev/progress/40) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) | | German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| French (Français) (fr_FR) | ![94%](https://geps.dev/progress/94) | | French (Français) (fr_FR) | ![93%](https://geps.dev/progress/93) |
| Spanish (Español) (es_ES) | ![95%](https://geps.dev/progress/95) | | Spanish (Español) (es_ES) | ![95%](https://geps.dev/progress/95) |
| Simplified Chinese (简体中文) (zh_CN) | ![95%](https://geps.dev/progress/95) | | Simplified Chinese (简体中文) (zh_CN) | ![95%](https://geps.dev/progress/95) |
| Traditional Chinese (繁體中文) (zh_TW) | ![94%](https://geps.dev/progress/94) | | Traditional Chinese (繁體中文) (zh_TW) | ![94%](https://geps.dev/progress/94) |
| Catalan (Català) (ca_CA) | ![49%](https://geps.dev/progress/49) | | Catalan (Català) (ca_CA) | ![49%](https://geps.dev/progress/49) |
| Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) | | Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) |
| Swedish (Svenska) (sv_SE) | ![40%](https://geps.dev/progress/40) | | Swedish (Svenska) (sv_SE) | ![40%](https://geps.dev/progress/40) |
| Polish (Polski) (pl_PL) | ![43%](https://geps.dev/progress/43) | | Polish (Polski) (pl_PL) | ![42%](https://geps.dev/progress/42) |
| Romanian (Română) (ro_RO) | ![40%](https://geps.dev/progress/40) | | Romanian (Română) (ro_RO) | ![39%](https://geps.dev/progress/39) |
| Korean (한국어) (ko_KR) | ![87%](https://geps.dev/progress/87) | | Korean (한국어) (ko_KR) | ![87%](https://geps.dev/progress/87) |
| Portuguese Brazilian (Português) (pt_BR) | ![61%](https://geps.dev/progress/61) | | Portuguese Brazilian (Português) (pt_BR) | ![61%](https://geps.dev/progress/61) |
| Russian (Русский) (ru_RU) | ![87%](https://geps.dev/progress/87) | | Russian (Русский) (ru_RU) | ![87%](https://geps.dev/progress/87) |
@ -183,16 +183,17 @@ Stirling PDF currently supports 28!
| Japanese (日本語) (ja_JP) | ![87%](https://geps.dev/progress/87) | | Japanese (日本語) (ja_JP) | ![87%](https://geps.dev/progress/87) |
| Dutch (Nederlands) (nl_NL) | ![85%](https://geps.dev/progress/85) | | Dutch (Nederlands) (nl_NL) | ![85%](https://geps.dev/progress/85) |
| Greek (Ελληνικά) (el_GR) | ![85%](https://geps.dev/progress/85) | | Greek (Ελληνικά) (el_GR) | ![85%](https://geps.dev/progress/85) |
| Turkish (Türkçe) (tr_TR) | ![98%](https://geps.dev/progress/98) | | Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) |
| Indonesia (Bahasa Indonesia) (id_ID) | ![79%](https://geps.dev/progress/79) | | Indonesia (Bahasa Indonesia) (id_ID) | ![78%](https://geps.dev/progress/78) |
| Hindi (हिंदी) (hi_IN) | ![79%](https://geps.dev/progress/79) | | Hindi (हिंदी) (hi_IN) | ![79%](https://geps.dev/progress/79) |
| Hungarian (Magyar) (hu_HU) | ![78%](https://geps.dev/progress/78) | | Hungarian (Magyar) (hu_HU) | ![78%](https://geps.dev/progress/78) |
| Bulgarian (Български) (bg_BG) | ![98%](https://geps.dev/progress/98) | | Bulgarian (Български) (bg_BG) | ![98%](https://geps.dev/progress/98) |
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) | ![81%](https://geps.dev/progress/81) | | Sebian Latin alphabet (Srpski) (sr_LATN_RS) | ![80%](https://geps.dev/progress/80) |
| Ukrainian (Українська) (uk_UA) | ![87%](https://geps.dev/progress/87) | | Ukrainian (Українська) (uk_UA) | ![86%](https://geps.dev/progress/86) |
| Slovakian (Slovensky) (sk_SK) | ![96%](https://geps.dev/progress/96) | | Slovakian (Slovensky) (sk_SK) | ![95%](https://geps.dev/progress/95) |
| Czech (Česky) (cs_CZ) | ![94%](https://geps.dev/progress/94) | | Czech (Česky) (cs_CZ) | ![94%](https://geps.dev/progress/94) |
| Croatian (Hrvatski) (hr_HR) | ![94%](https://geps.dev/progress/94) | | Croatian (Hrvatski) (hr_HR) | ![98%](https://geps.dev/progress/98) |
| Norwegian (Norsk) (no_NB) | ![98%](https://geps.dev/progress/98) |
## Contributing (creating issues, translations, fixing bugs, etc.) ## Contributing (creating issues, translations, fixing bugs, etc.)

View File

@ -12,7 +12,7 @@ plugins {
import com.github.jk1.license.render.* import com.github.jk1.license.render.*
group = 'stirling.software' group = 'stirling.software'
version = '0.25.1' version = '0.25.2'
//17 is lowest but we support and recommend 21 //17 is lowest but we support and recommend 21
sourceCompatibility = '17' sourceCompatibility = '17'
@ -171,14 +171,14 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok:1.18.32' annotationProcessor 'org.projectlombok:lombok:1.18.32'
} }
tasks.withType(JavaCompile) { tasks.withType(JavaCompile).configureEach {
dependsOn 'spotlessApply' dependsOn 'spotlessApply'
} }
compileJava { compileJava {
options.compilerArgs << '-parameters' options.compilerArgs << '-parameters'
} }
task writeVersion { task writeVersion {
def propsFile = file('src/main/resources/version.properties') def propsFile = file('src/main/resources/version.properties')
def props = new Properties() def props = new Properties()
props.setProperty('version', version) props.setProperty('version', version)
@ -195,8 +195,6 @@ swaggerhubUpload {
oas '3.0.0' // The version of the OpenAPI Specification you're using oas '3.0.0' // The version of the OpenAPI Specification you're using
} }
jar { jar {
enabled = false enabled = false
manifest { manifest {
@ -210,6 +208,6 @@ tasks.named('test') {
useJUnitPlatform() useJUnitPlatform()
} }
task printVersion { task printVersion {
println project.version println project.version
} }

View File

@ -1,5 +1,5 @@
apiVersion: v2 apiVersion: v2
appVersion: 0.25.1 appVersion: 0.25.2
description: locally hosted web application that allows you to perform various operations description: locally hosted web application that allows you to perform various operations
on PDF files on PDF files
home: https://github.com/Stirling-Tools/Stirling-PDF home: https://github.com/Stirling-Tools/Stirling-PDF

View File

@ -13,7 +13,7 @@ services:
timeout: 10s timeout: 10s
retries: 16 retries: 16
ports: ports:
- 8080:8080 - "8080:8080"
volumes: volumes:
- /stirling/latest/data:/usr/share/tessdata:rw - /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw

View File

@ -13,7 +13,7 @@ services:
timeout: 10s timeout: 10s
retries: 16 retries: 16
ports: ports:
- 8080:8080 - "8080:8080"
volumes: volumes:
- /stirling/latest/data:/usr/share/tessdata:rw - /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw

View File

@ -13,7 +13,7 @@ services:
timeout: 10s timeout: 10s
retries: 16 retries: 16
ports: ports:
- 8080:8080 - "8080:8080"
volumes: volumes:
- /stirling/latest/data:/usr/share/tessdata:rw - /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw

View File

@ -13,7 +13,7 @@ services:
timeout: 10s timeout: 10s
retries: 16 retries: 16
ports: ports:
- 8080:8080 - "8080:8080"
volumes: volumes:
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw - /stirling/latest/logs:/logs:rw

View File

@ -13,7 +13,7 @@ services:
timeout: 10s timeout: 10s
retries: 16 retries: 16
ports: ports:
- 8080:8080 - "8080:8080"
volumes: volumes:
- /stirling/latest/data:/usr/share/tessdata:rw - /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw - /stirling/latest/config:/configs:rw

View File

@ -15,7 +15,10 @@ ignore = [
[cs_CZ] [cs_CZ]
ignore = [ ignore = [
'info',
'language.direction', 'language.direction',
'pipeline.header',
'text',
] ]
[de_DE] [de_DE]
@ -65,6 +68,16 @@ ignore = [
'language.direction', 'language.direction',
] ]
[hr_HR]
ignore = [
'font',
'home.pipeline.title',
'info',
'language.direction',
'pdfOrganiser.tags',
'showJS.tags',
]
[hu_HU] [hu_HU]
ignore = [ ignore = [
'language.direction', 'language.direction',
@ -103,6 +116,11 @@ ignore = [
'language.direction', 'language.direction',
] ]
[no_NB]
ignore = [
'language.direction',
]
[pl_PL] [pl_PL]
ignore = [ ignore = [
'language.direction', 'language.direction',

View File

@ -6,11 +6,15 @@ import java.net.Socket;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.pixee.security.SystemCommand; import io.github.pixee.security.SystemCommand;
public class LibreOfficeListener { public class LibreOfficeListener {
private static final long ACTIVITY_TIMEOUT = 20 * 60 * 1000; // 20 minutes private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class);
private static final long ACTIVITY_TIMEOUT = 20L * 60 * 1000; // 20 minutes
private static final LibreOfficeListener INSTANCE = new LibreOfficeListener(); private static final LibreOfficeListener INSTANCE = new LibreOfficeListener();
private static final int LISTENER_PORT = 2002; private static final int LISTENER_PORT = 2002;
@ -27,14 +31,12 @@ public class LibreOfficeListener {
private LibreOfficeListener() {} private LibreOfficeListener() {}
private boolean isListenerRunning() { private boolean isListenerRunning() {
try { System.out.println("waiting for listener to start");
System.out.println("waiting for listener to start"); try (Socket socket = new Socket()) {
Socket socket = new Socket();
socket.connect( socket.connect(
new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
socket.close();
return true; return true;
} catch (IOException e) { } catch (Exception e) {
return false; return false;
} }
} }
@ -63,6 +65,7 @@ public class LibreOfficeListener {
try { try {
Thread.sleep(5000); // Check for inactivity every 5 seconds Thread.sleep(5000); // Check for inactivity every 5 seconds
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt();
break; break;
} }
} }
@ -80,8 +83,8 @@ public class LibreOfficeListener {
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// TODO Auto-generated catch block Thread.currentThread().interrupt();
e.printStackTrace(); logger.error("exception", e);
} // Check every 1 second } // Check every 1 second
} }
} }

View File

@ -7,6 +7,8 @@ import java.nio.file.Paths;
import java.util.Properties; import java.util.Properties;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -24,6 +26,8 @@ import stirling.software.SPDF.model.ApplicationProperties;
@Lazy @Lazy
public class AppConfig { public class AppConfig {
private static final Logger logger = LoggerFactory.getLogger(AppConfig.class);
@Autowired ApplicationProperties applicationProperties; @Autowired ApplicationProperties applicationProperties;
@Bean @Bean
@ -56,7 +60,7 @@ public class AppConfig {
props.load(resource.getInputStream()); props.load(resource.getInputStream());
return props.getProperty("version"); return props.getProperty("version");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.error("exception", e);
} }
return "0.0.0"; return "0.0.0";
} }

View File

@ -1,15 +1,12 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.BufferedReader;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
@ -47,61 +44,49 @@ public class ConfigInitializer
} }
} }
} else { } else {
// Load the template content from classpath // Path templatePath =
List<String> templateLines; // Paths.get(
try (InputStream in = // getClass()
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { // .getClassLoader()
if (in == null) { // .getResource("settings.yml.template")
throw new FileNotFoundException( // .toURI());
"Resource file not found: settings.yml.template"); // Path userPath = Paths.get("configs", "settings.yml");
} //
templateLines = new ArrayList<>(); // List<String> templateLines = Files.readAllLines(templatePath);
try (var reader = new InputStreamReader(in)) { // List<String> userLines =
try (var bufferedReader = new BufferedReader(reader)) { // Files.exists(userPath) ? Files.readAllLines(userPath) : new
String line; // ArrayList<>();
while ((line = bufferedReader.readLine()) != null) { //
templateLines.add(line); // List<String> resultLines = new ArrayList<>();
} // int position = 0;
} // for (String templateLine : templateLines) {
} // // Check if the line is a comment
} // if (templateLine.trim().startsWith("#")) {
// String entry = templateLine.trim().substring(1).trim();
// Read the user settings file if it exists // if (!entry.isEmpty()) {
Path userPath = Paths.get("configs", "settings.yml"); // // Check if this comment has been uncommented in userLines
List<String> userLines = // String key = entry.split(":")[0].trim();
Files.exists(userPath) ? Files.readAllLines(userPath) : new ArrayList<>(); // addLine(resultLines, userLines, templateLine, key, position);
// } else {
List<String> resultLines = new ArrayList<>(); // resultLines.add(templateLine);
int position = 0; // }
for (String templateLine : templateLines) { // }
// Check if the line is a comment // // Check if the line is a key-value pair
if (templateLine.trim().startsWith("#")) { // else if (templateLine.contains(":")) {
String entry = templateLine.trim().substring(1).trim(); // String key = templateLine.split(":")[0].trim();
if (!entry.isEmpty()) { // addLine(resultLines, userLines, templateLine, key, position);
// Check if this comment has been uncommented in userLines // }
String key = entry.split(":")[0].trim(); // // Handle empty lines
addLine(resultLines, userLines, templateLine, key, position); // else if (templateLine.trim().length() == 0) {
} else { // resultLines.add("");
resultLines.add(templateLine); // }
} // position++;
} // }
// Check if the line is a key-value pair //
else if (templateLine.contains(":")) { // // Write the result to the user settings file
String key = templateLine.split(":")[0].trim(); // Files.write(userPath, resultLines);
addLine(resultLines, userLines, templateLine, key, position);
}
// Handle empty lines
else if (templateLine.trim().length() == 0) {
resultLines.add("");
}
position++;
}
// Write the result to the user settings file
Files.write(userPath, resultLines);
} }
// Ensure the custom settings file exists
Path customSettingsPath = Paths.get("configs", "custom_settings.yml"); Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
if (!Files.exists(customSettingsPath)) { if (!Files.exists(customSettingsPath)) {
Files.createFile(customSettingsPath); Files.createFile(customSettingsPath);

View File

@ -1,16 +1,18 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Map; import java.util.Map;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.IEngineConfiguration; import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver; import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver;
import org.thymeleaf.templateresource.ClassLoaderTemplateResource;
import org.thymeleaf.templateresource.FileTemplateResource; import org.thymeleaf.templateresource.FileTemplateResource;
import org.thymeleaf.templateresource.ITemplateResource; import org.thymeleaf.templateresource.ITemplateResource;
import stirling.software.SPDF.model.InputStreamTemplateResource;
public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver { public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver {
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
@ -40,9 +42,13 @@ public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateRe
} }
return new ClassLoaderTemplateResource( InputStream inputStream =
Thread.currentThread().getContextClassLoader(), Thread.currentThread()
"classpath:/templates/" + resourceName, .getContextClassLoader()
characterEncoding); .getResourceAsStream("templates/" + resourceName);
if (inputStream != null) {
return new InputStreamTemplateResource(inputStream, "UTF-8");
}
return null;
} }
} }

View File

@ -36,7 +36,6 @@ public class MetricsFilter extends OncePerRequestFilter {
|| uri.startsWith("/v1/api-docs") || uri.startsWith("/v1/api-docs")
|| uri.endsWith("robots.txt") || uri.endsWith("robots.txt")
|| uri.startsWith("/images") || uri.startsWith("/images")
|| uri.startsWith("/images")
|| uri.endsWith(".png") || uri.endsWith(".png")
|| uri.endsWith(".ico") || uri.endsWith(".ico")
|| uri.endsWith(".css") || uri.endsWith(".css")

View File

@ -49,10 +49,12 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
} }
String username = request.getParameter("username"); String username = request.getParameter("username");
if (username != null && !isDemoUser(username)) { Optional<User> optUser = userService.findByUsernameIgnoreCase(username);
if (username != null && optUser.isPresent() && !isDemoUser(optUser)) {
logger.info( logger.info(
"Remaining attempts for user {}: {}", "Remaining attempts for user {}: {}",
username, optUser.get().getUsername(),
loginAttemptService.getRemainingAttempts(username)); loginAttemptService.getRemainingAttempts(username));
loginAttemptService.loginFailed(username); loginAttemptService.loginFailed(username);
if (loginAttemptService.isBlocked(username) if (loginAttemptService.isBlocked(username)
@ -70,8 +72,7 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
super.onAuthenticationFailure(request, response, exception); super.onAuthenticationFailure(request, response, exception);
} }
private boolean isDemoUser(String username) { private boolean isDemoUser(Optional<User> user) {
Optional<User> user = userService.findByUsernameIgnoreCase(username);
return user.isPresent() return user.isPresent()
&& user.get().getAuthorities().stream() && user.get().getAuthorities().stream()
.anyMatch(authority -> "ROLE_DEMO_USER".equals(authority.getAuthority())); .anyMatch(authority -> "ROLE_DEMO_USER".equals(authority.getAuthority()));

View File

@ -33,7 +33,6 @@ public class LoginAttemptService {
} }
public void loginSucceeded(String key) { public void loginSucceeded(String key) {
logger.info(key + " " + attemptsCache.mappingCount());
if (key == null || key.trim().isEmpty()) { if (key == null || key.trim().isEmpty()) {
return; return;
} }

View File

@ -238,7 +238,7 @@ public class SecurityConfiguration {
GoogleProvider google = client.getGoogle(); GoogleProvider google = client.getGoogle();
return google != null && google.isSettingsValid() return google != null && google.isSettingsValid()
? Optional.of( ? Optional.of(
ClientRegistration.withRegistrationId("google") ClientRegistration.withRegistrationId(google.getName())
.clientId(google.getClientId()) .clientId(google.getClientId())
.clientSecret(google.getClientSecret()) .clientSecret(google.getClientSecret())
.scope(google.getScopes()) .scope(google.getScopes())
@ -246,8 +246,8 @@ public class SecurityConfiguration {
.tokenUri(google.getTokenuri()) .tokenUri(google.getTokenuri())
.userInfoUri(google.getUserinfouri()) .userInfoUri(google.getUserinfouri())
.userNameAttributeName(google.getUseAsUsername()) .userNameAttributeName(google.getUseAsUsername())
.clientName("Google") .clientName(google.getClientName())
.redirectUri("{baseUrl}/login/oauth2/code/google") .redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
.authorizationGrantType( .authorizationGrantType(
org.springframework.security.oauth2.core org.springframework.security.oauth2.core
.AuthorizationGrantType.AUTHORIZATION_CODE) .AuthorizationGrantType.AUTHORIZATION_CODE)
@ -269,12 +269,12 @@ public class SecurityConfiguration {
return keycloak != null && keycloak.isSettingsValid() return keycloak != null && keycloak.isSettingsValid()
? Optional.of( ? Optional.of(
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer()) ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
.registrationId("keycloak") .registrationId(keycloak.getName())
.clientId(keycloak.getClientId()) .clientId(keycloak.getClientId())
.clientSecret(keycloak.getClientSecret()) .clientSecret(keycloak.getClientSecret())
.scope(keycloak.getScopes()) .scope(keycloak.getScopes())
.userNameAttributeName(keycloak.getUseAsUsername()) .userNameAttributeName(keycloak.getUseAsUsername())
.clientName("Keycloak") .clientName(keycloak.getClientName())
.build()) .build())
: Optional.empty(); : Optional.empty();
} }
@ -291,7 +291,7 @@ public class SecurityConfiguration {
GithubProvider github = client.getGithub(); GithubProvider github = client.getGithub();
return github != null && github.isSettingsValid() return github != null && github.isSettingsValid()
? Optional.of( ? Optional.of(
ClientRegistration.withRegistrationId("github") ClientRegistration.withRegistrationId(github.getName())
.clientId(github.getClientId()) .clientId(github.getClientId())
.clientSecret(github.getClientSecret()) .clientSecret(github.getClientSecret())
.scope(github.getScopes()) .scope(github.getScopes())
@ -299,8 +299,8 @@ public class SecurityConfiguration {
.tokenUri(github.getTokenuri()) .tokenUri(github.getTokenuri())
.userInfoUri(github.getUserinfouri()) .userInfoUri(github.getUserinfouri())
.userNameAttributeName(github.getUseAsUsername()) .userNameAttributeName(github.getUseAsUsername())
.clientName("GitHub") .clientName(github.getClientName())
.redirectUri("{baseUrl}/login/oauth2/code/github") .redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
.authorizationGrantType( .authorizationGrantType(
org.springframework.security.oauth2.core org.springframework.security.oauth2.core
.AuthorizationGrantType.AUTHORIZATION_CODE) .AuthorizationGrantType.AUTHORIZATION_CODE)

View File

@ -52,7 +52,7 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
issuer = provider.getIssuer(); issuer = provider.getIssuer();
clientId = provider.getClientId(); clientId = provider.getClientId();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); logger.error("exception", e);
} }
} else { } else {
@ -60,13 +60,13 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
issuer = oauth.getIssuer(); issuer = oauth.getIssuer();
clientId = oauth.getClientId(); clientId = oauth.getClientId();
} }
String errorMessage = "";
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) { if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
param = "erroroauth=oauth2AuthenticationErrorWeb"; param = "erroroauth=oauth2AuthenticationErrorWeb";
} else if (request.getParameter("error") != null) { } else if ((errorMessage = request.getParameter("error")) != null) {
param = "error=" + request.getParameter("error"); param = "error=" + sanitizeInput(errorMessage);
} else if (request.getParameter("erroroauth") != null) { } else if ((errorMessage = request.getParameter("erroroauth")) != null) {
param = "erroroauth=" + request.getParameter("erroroauth"); param = "erroroauth=" + sanitizeInput(errorMessage);
} else if (request.getParameter("oauth2AutoCreateDisabled") != null) { } else if (request.getParameter("oauth2AutoCreateDisabled") != null) {
param = "error=oauth2AutoCreateDisabled"; param = "error=oauth2AutoCreateDisabled";
} }
@ -81,7 +81,7 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
logger.info("Session invalidated: " + sessionId); logger.info("Session invalidated: " + sessionId);
} }
switch (registrationId) { switch (registrationId.toLowerCase()) {
case "keycloak": case "keycloak":
// Add Keycloak specific logout URL if needed // Add Keycloak specific logout URL if needed
String logoutUrl = String logoutUrl =
@ -115,4 +115,8 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
break; break;
} }
} }
private String sanitizeInput(String input) {
return input.replaceAll("[^a-zA-Z0-9 ]", "");
}
} }

View File

@ -16,6 +16,8 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import stirling.software.SPDF.config.security.LoginAttemptService; import stirling.software.SPDF.config.security.LoginAttemptService;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> { public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
@ -41,11 +43,27 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
@Override @Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
String usernameAttribute = OAUTH2 oauth2 = applicationProperties.getSecurity().getOAUTH2();
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername(); String usernameAttribute = oauth2.getUseAsUsername();
if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) {
Client client = oauth2.getClient();
if (client != null && client.getKeycloak() != null) {
usernameAttribute = client.getKeycloak().getUseAsUsername();
} else {
usernameAttribute = "email";
}
}
try { try {
OidcUser user = delegate.loadUser(userRequest); OidcUser user = delegate.loadUser(userRequest);
String username = user.getUserInfo().getClaimAsString(usernameAttribute); String username = user.getUserInfo().getClaimAsString(usernameAttribute);
// Check if the username claim is null or empty
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException(
"Claim '" + usernameAttribute + "' cannot be null or empty");
}
Optional<User> duser = userService.findByUsernameIgnoreCase(username); Optional<User> duser = userService.findByUsernameIgnoreCase(username);
if (duser.isPresent()) { if (duser.isPresent()) {
if (loginAttemptService.isBlocked(username)) { if (loginAttemptService.isBlocked(username)) {
@ -56,13 +74,14 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
throw new IllegalArgumentException("Password must not be null"); throw new IllegalArgumentException("Password must not be null");
} }
} }
// Return a new OidcUser with adjusted attributes // Return a new OidcUser with adjusted attributes
return new DefaultOidcUser( return new DefaultOidcUser(
user.getAuthorities(), user.getAuthorities(),
userRequest.getIdToken(), userRequest.getIdToken(),
user.getUserInfo(), user.getUserInfo(),
usernameAttribute); usernameAttribute);
} catch (java.lang.IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.error("Error loading OIDC user: {}", e.getMessage()); logger.error("Error loading OIDC user: {}", e.getMessage());
throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e); throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
} catch (Exception e) { } catch (Exception e) {

View File

@ -18,6 +18,8 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@ -38,6 +40,9 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs") @Tag(name = "General", description = "General APIs")
public class SplitPdfBySectionsController { public class SplitPdfBySectionsController {
private static final Logger logger =
LoggerFactory.getLogger(SplitPdfBySectionsController.class);
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data") @PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Split PDF pages into smaller sections", summary = "Split PDF pages into smaller sections",
@ -92,7 +97,7 @@ public class SplitPdfBySectionsController {
if (sectionNum == horiz * verti) pageNum++; if (sectionNum == horiz * verti) pageNum++;
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); logger.error("exception", e);
} finally { } finally {
data = Files.readAllBytes(zipFile); data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile); Files.deleteIfExists(zipFile);

View File

@ -10,6 +10,8 @@ import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@ -31,6 +33,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs") @Tag(name = "General", description = "General APIs")
public class SplitPdfBySizeController { public class SplitPdfBySizeController {
private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySizeController.class);
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data") @PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
@Operation( @Operation(
summary = "Auto split PDF pages into separate documents based on size or count", summary = "Auto split PDF pages into separate documents based on size or count",
@ -66,7 +70,7 @@ public class SplitPdfBySizeController {
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); logger.error("exception", e);
} finally { } finally {
data = Files.readAllBytes(zipFile); data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile); Files.deleteIfExists(zipFile);

View File

@ -66,46 +66,46 @@ public class UserController {
RedirectAttributes redirectAttributes) { RedirectAttributes redirectAttributes) {
if (!userService.isUsernameValid(newUsername)) { if (!userService.isUsernameValid(newUsername)) {
return new RedirectView("/account?messageType=invalidUsername"); return new RedirectView("/account?messageType=invalidUsername", true);
} }
if (principal == null) { if (principal == null) {
return new RedirectView("/account?messageType=notAuthenticated"); return new RedirectView("/account?messageType=notAuthenticated", true);
} }
// The username MUST be unique when renaming // The username MUST be unique when renaming
Optional<User> userOpt = userService.findByUsername(principal.getName()); Optional<User> userOpt = userService.findByUsername(principal.getName());
if (userOpt == null || userOpt.isEmpty()) { if (userOpt == null || userOpt.isEmpty()) {
return new RedirectView("/account?messageType=userNotFound"); return new RedirectView("/account?messageType=userNotFound", true);
} }
User user = userOpt.get(); User user = userOpt.get();
if (user.getUsername().equals(newUsername)) { if (user.getUsername().equals(newUsername)) {
return new RedirectView("/account?messageType=usernameExists"); return new RedirectView("/account?messageType=usernameExists", true);
} }
if (!userService.isPasswordCorrect(user, currentPassword)) { if (!userService.isPasswordCorrect(user, currentPassword)) {
return new RedirectView("/account?messageType=incorrectPassword"); return new RedirectView("/account?messageType=incorrectPassword", true);
} }
if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) { if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
return new RedirectView("/account?messageType=usernameExists"); return new RedirectView("/account?messageType=usernameExists", true);
} }
if (newUsername != null && newUsername.length() > 0) { if (newUsername != null && newUsername.length() > 0) {
try { try {
userService.changeUsername(user, newUsername); userService.changeUsername(user, newUsername);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return new RedirectView("/account?messageType=invalidUsername"); return new RedirectView("/account?messageType=invalidUsername", true);
} }
} }
// Logout using Spring's utility // Logout using Spring's utility
new SecurityContextLogoutHandler().logout(request, response, null); new SecurityContextLogoutHandler().logout(request, response, null);
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED); return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);
} }
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@ -118,19 +118,19 @@ public class UserController {
HttpServletResponse response, HttpServletResponse response,
RedirectAttributes redirectAttributes) { RedirectAttributes redirectAttributes) {
if (principal == null) { if (principal == null) {
return new RedirectView("/change-creds?messageType=notAuthenticated"); return new RedirectView("/change-creds?messageType=notAuthenticated", true);
} }
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName()); Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
if (userOpt == null || userOpt.isEmpty()) { if (userOpt == null || userOpt.isEmpty()) {
return new RedirectView("/change-creds?messageType=userNotFound"); return new RedirectView("/change-creds?messageType=userNotFound", true);
} }
User user = userOpt.get(); User user = userOpt.get();
if (!userService.isPasswordCorrect(user, currentPassword)) { if (!userService.isPasswordCorrect(user, currentPassword)) {
return new RedirectView("/change-creds?messageType=incorrectPassword"); return new RedirectView("/change-creds?messageType=incorrectPassword", true);
} }
userService.changePassword(user, newPassword); userService.changePassword(user, newPassword);
@ -138,7 +138,7 @@ public class UserController {
// Logout using Spring's utility // Logout using Spring's utility
new SecurityContextLogoutHandler().logout(request, response, null); new SecurityContextLogoutHandler().logout(request, response, null);
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED); return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);
} }
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@ -151,19 +151,19 @@ public class UserController {
HttpServletResponse response, HttpServletResponse response,
RedirectAttributes redirectAttributes) { RedirectAttributes redirectAttributes) {
if (principal == null) { if (principal == null) {
return new RedirectView("/account?messageType=notAuthenticated"); return new RedirectView("/account?messageType=notAuthenticated", true);
} }
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName()); Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
if (userOpt == null || userOpt.isEmpty()) { if (userOpt == null || userOpt.isEmpty()) {
return new RedirectView("/account?messageType=userNotFound"); return new RedirectView("/account?messageType=userNotFound", true);
} }
User user = userOpt.get(); User user = userOpt.get();
if (!userService.isPasswordCorrect(user, currentPassword)) { if (!userService.isPasswordCorrect(user, currentPassword)) {
return new RedirectView("/account?messageType=incorrectPassword"); return new RedirectView("/account?messageType=incorrectPassword", true);
} }
userService.changePassword(user, newPassword); userService.changePassword(user, newPassword);
@ -171,7 +171,7 @@ public class UserController {
// Logout using Spring's utility // Logout using Spring's utility
new SecurityContextLogoutHandler().logout(request, response, null); new SecurityContextLogoutHandler().logout(request, response, null);
return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED); return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);
} }
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@ -204,7 +204,7 @@ public class UserController {
boolean forceChange) { boolean forceChange) {
if (!userService.isUsernameValid(username)) { if (!userService.isUsernameValid(username)) {
return new RedirectView("/addUsers?messageType=invalidUsername"); return new RedirectView("/addUsers?messageType=invalidUsername", true);
} }
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username); Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
@ -212,26 +212,27 @@ public class UserController {
if (userOpt.isPresent()) { if (userOpt.isPresent()) {
User user = userOpt.get(); User user = userOpt.get();
if (user != null && user.getUsername().equalsIgnoreCase(username)) { if (user != null && user.getUsername().equalsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=usernameExists"); return new RedirectView("/addUsers?messageType=usernameExists", true);
} }
} }
if (userService.usernameExistsIgnoreCase(username)) { if (userService.usernameExistsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=usernameExists"); return new RedirectView("/addUsers?messageType=usernameExists", true);
} }
try { try {
// Validate the role // Validate the role
Role roleEnum = Role.fromString(role); Role roleEnum = Role.fromString(role);
if (roleEnum == Role.INTERNAL_API_USER) { if (roleEnum == Role.INTERNAL_API_USER) {
// If the role is INTERNAL_API_USER, reject the request // If the role is INTERNAL_API_USER, reject the request
return new RedirectView("/addUsers?messageType=invalidRole"); return new RedirectView("/addUsers?messageType=invalidRole", true);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// If the role ID is not valid, redirect with an error message // If the role ID is not valid, redirect with an error message
return new RedirectView("/addUsers?messageType=invalidRole"); return new RedirectView("/addUsers?messageType=invalidRole", true);
} }
userService.saveUser(username, password, role, forceChange); userService.saveUser(username, password, role, forceChange);
return new RedirectView("/addUsers"); // Redirect to account page after adding the user return new RedirectView(
"/addUsers", true); // Redirect to account page after adding the user
} }
@PreAuthorize("hasRole('ROLE_ADMIN')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@ -244,33 +245,34 @@ public class UserController {
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username); Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
if (!userOpt.isPresent()) { if (!userOpt.isPresent()) {
return new RedirectView("/addUsers?messageType=userNotFound"); return new RedirectView("/addUsers?messageType=userNotFound", true);
} }
if (!userService.usernameExistsIgnoreCase(username)) { if (!userService.usernameExistsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=userNotFound"); return new RedirectView("/addUsers?messageType=userNotFound", true);
} }
// Get the currently authenticated username // Get the currently authenticated username
String currentUsername = authentication.getName(); String currentUsername = authentication.getName();
// Check if the provided username matches the current session's username // Check if the provided username matches the current session's username
if (currentUsername.equalsIgnoreCase(username)) { if (currentUsername.equalsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=downgradeCurrentUser"); return new RedirectView("/addUsers?messageType=downgradeCurrentUser", true);
} }
try { try {
// Validate the role // Validate the role
Role roleEnum = Role.fromString(role); Role roleEnum = Role.fromString(role);
if (roleEnum == Role.INTERNAL_API_USER) { if (roleEnum == Role.INTERNAL_API_USER) {
// If the role is INTERNAL_API_USER, reject the request // If the role is INTERNAL_API_USER, reject the request
return new RedirectView("/addUsers?messageType=invalidRole"); return new RedirectView("/addUsers?messageType=invalidRole", true);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// If the role ID is not valid, redirect with an error message // If the role ID is not valid, redirect with an error message
return new RedirectView("/addUsers?messageType=invalidRole"); return new RedirectView("/addUsers?messageType=invalidRole", true);
} }
User user = userOpt.get(); User user = userOpt.get();
userService.changeRole(user, role); userService.changeRole(user, role);
return new RedirectView("/addUsers"); // Redirect to account page after adding the user return new RedirectView(
"/addUsers", true); // Redirect to account page after adding the user
} }
@PreAuthorize("hasRole('ROLE_ADMIN')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@ -279,7 +281,7 @@ public class UserController {
@PathVariable(name = "username") String username, Authentication authentication) { @PathVariable(name = "username") String username, Authentication authentication) {
if (!userService.usernameExistsIgnoreCase(username)) { if (!userService.usernameExistsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=deleteUsernameExists"); return new RedirectView("/addUsers?messageType=deleteUsernameExists", true);
} }
// Get the currently authenticated username // Get the currently authenticated username
@ -287,11 +289,11 @@ public class UserController {
// Check if the provided username matches the current session's username // Check if the provided username matches the current session's username
if (currentUsername.equalsIgnoreCase(username)) { if (currentUsername.equalsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=deleteCurrentUser"); return new RedirectView("/addUsers?messageType=deleteCurrentUser", true);
} }
invalidateUserSessions(username); invalidateUserSessions(username);
userService.deleteUser(username); userService.deleteUser(username);
return new RedirectView("/addUsers"); return new RedirectView("/addUsers", true);
} }
@Autowired private SessionRegistry sessionRegistry; @Autowired private SessionRegistry sessionRegistry;

View File

@ -15,6 +15,8 @@ import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@ -43,6 +45,7 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
public class AutoSplitPdfController { public class AutoSplitPdfController {
private static final Logger logger = LoggerFactory.getLogger(AutoSplitPdfController.class);
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF"; private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF"; private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
@ -115,7 +118,7 @@ public class AutoSplitPdfController {
zipOut.closeEntry(); zipOut.closeEntry();
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); logger.error("exception", e);
} finally { } finally {
data = Files.readAllBytes(zipFile); data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile); Files.deleteIfExists(zipFile);

View File

@ -106,7 +106,7 @@ public class BlankPageController {
.replaceFirst("[.][^.]+$", "") .replaceFirst("[.][^.]+$", "")
+ "_blanksRemoved.pdf"); + "_blanksRemoved.pdf");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.error("exception", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
} finally { } finally {
if (document != null) document.close(); if (document != null) document.close();

View File

@ -110,8 +110,8 @@ public class FakeScanControllerWIP {
private BufferedImage rotate(BufferedImage image, double rotation) { private BufferedImage rotate(BufferedImage image, double rotation) {
double rotationRequired = Math.toRadians(rotation); double rotationRequired = Math.toRadians(rotation);
double locationX = image.getWidth() / 2; double locationX = (double) image.getWidth() / 2;
double locationY = image.getHeight() / 2; double locationY = (double) image.getHeight() / 2;
AffineTransform tx = AffineTransform tx =
AffineTransform.getRotateInstance(rotationRequired, locationX, locationY); AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC); AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);
@ -127,8 +127,8 @@ public class FakeScanControllerWIP {
for (int i = -radius; i <= radius; i++) { for (int i = -radius; i <= radius; i++) {
for (int j = -radius; j <= radius; j++) { for (int j = -radius; j <= radius; j++) {
double xDistance = i * i; double xDistance = (double) i * i;
double yDistance = j * j; double yDistance = (double) j * j;
double g = Math.exp(-(xDistance + yDistance) / (2 * sigma * sigma)); double g = Math.exp(-(xDistance + yDistance) / (2 * sigma * sigma));
data[(i + radius) * size + j + radius] = (float) g; data[(i + radius) * size + j + radius] = (float) g;
sum += g; sum += g;
@ -137,7 +137,7 @@ public class FakeScanControllerWIP {
// Normalize the kernel // Normalize the kernel
for (int i = 0; i < data.length; i++) { for (int i = 0; i < data.length; i++) {
data[i] /= sum; if (sum != 0) data[i] /= sum;
} }
Kernel kernel = new Kernel(size, size, data); Kernel kernel = new Kernel(size, size, data);
@ -166,7 +166,7 @@ public class FakeScanControllerWIP {
0, 0,
new Color(0, 0, 0, 1f), new Color(0, 0, 0, 1f),
0, 0,
featherRadius * 2, featherRadius * 2f,
new Color(0, 0, 0, 0f))); new Color(0, 0, 0, 0f)));
g2.fillRect(0, 0, width, featherRadius); g2.fillRect(0, 0, width, featherRadius);
@ -174,7 +174,7 @@ public class FakeScanControllerWIP {
g2.setPaint( g2.setPaint(
new GradientPaint( new GradientPaint(
0, 0,
height - featherRadius * 2, height - featherRadius * 2f,
new Color(0, 0, 0, 0f), new Color(0, 0, 0, 0f),
0, 0,
height, height,
@ -187,7 +187,7 @@ public class FakeScanControllerWIP {
0, 0,
0, 0,
new Color(0, 0, 0, 1f), new Color(0, 0, 0, 1f),
featherRadius * 2, featherRadius * 2f,
0, 0,
new Color(0, 0, 0, 0f))); new Color(0, 0, 0, 0f)));
g2.fillRect(0, 0, featherRadius, height); g2.fillRect(0, 0, featherRadius, height);
@ -195,7 +195,7 @@ public class FakeScanControllerWIP {
// Right edge // Right edge
g2.setPaint( g2.setPaint(
new GradientPaint( new GradientPaint(
width - featherRadius * 2, width - featherRadius * 2f,
0, 0,
new Color(0, 0, 0, 0f), new Color(0, 0, 0, 0f),
width, width,
@ -244,7 +244,7 @@ public class FakeScanControllerWIP {
int y2 = y1 + random.nextInt(20) - 10; int y2 = y1 + random.nextInt(20) - 10;
Path2D.Double hair = new Path2D.Double(); Path2D.Double hair = new Path2D.Double();
hair.moveTo(x1, y1); hair.moveTo(x1, y1);
hair.curveTo(x1, y1, (x1 + x2) / 2, (y1 + y2) / 2, x2, y2); hair.curveTo(x1, y1, (double) (x1 + x2) / 2, (double) (y1 + y2) / 2, x2, y2);
g2d.draw(hair); g2d.draw(hair);
} }

View File

@ -12,6 +12,8 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -33,6 +35,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
public class FlattenController { public class FlattenController {
private static final Logger logger = LoggerFactory.getLogger(FlattenController.class);
@PostMapping(consumes = "multipart/form-data", value = "/flatten") @PostMapping(consumes = "multipart/form-data", value = "/flatten")
@Operation( @Operation(
summary = "Flatten PDF form fields or full page", summary = "Flatten PDF form fields or full page",
@ -73,7 +77,7 @@ public class FlattenController {
contentStream.drawImage(pdImage, 0, 0, pageWidth, pageHeight); contentStream.drawImage(pdImage, 0, 0, pageWidth, pageHeight);
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.error("exception", e);
} }
} }
PdfUtils.setMetadataToPdf(newDocument, metadata); PdfUtils.setMetadataToPdf(newDocument, metadata);

View File

@ -11,6 +11,8 @@ import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation; import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -30,6 +32,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
public class MetadataController { public class MetadataController {
private static final Logger logger = LoggerFactory.getLogger(MetadataController.class);
private String checkUndefined(String entry) { private String checkUndefined(String entry) {
// Check if the string is "undefined" // Check if the string is "undefined"
if ("undefined".equals(entry)) { if ("undefined".equals(entry)) {
@ -136,7 +140,7 @@ public class MetadataController {
creationDateCal.setTime( creationDateCal.setTime(
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate)); new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate));
} catch (ParseException e) { } catch (ParseException e) {
e.printStackTrace(); logger.error("exception", e);
} }
info.setCreationDate(creationDateCal); info.setCreationDate(creationDateCal);
} else { } else {
@ -148,7 +152,7 @@ public class MetadataController {
modificationDateCal.setTime( modificationDateCal.setTime(
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate)); new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate));
} catch (ParseException e) { } catch (ParseException e) {
e.printStackTrace(); logger.error("exception", e);
} }
info.setModificationDate(modificationDateCal); info.setModificationDate(modificationDateCal);
} else { } else {

View File

@ -148,7 +148,7 @@ public class CertSignController {
doc.addSignature(signature, instance); doc.addSignature(signature, instance);
doc.saveIncremental(output); doc.saveIncremental(output);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); logger.error("exception", e);
} }
} }

View File

@ -56,6 +56,8 @@ import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.xml.DomXmpParser; import org.apache.xmpbox.xml.DomXmpParser;
import org.apache.xmpbox.xml.XmpParsingException; import org.apache.xmpbox.xml.XmpParsingException;
import org.apache.xmpbox.xml.XmpSerializer; import org.apache.xmpbox.xml.XmpSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@ -79,6 +81,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Security", description = "Security APIs") @Tag(name = "Security", description = "Security APIs")
public class GetInfoOnPDF { public class GetInfoOnPDF {
private static final Logger logger = LoggerFactory.getLogger(GetInfoOnPDF.class);
static ObjectMapper objectMapper = new ObjectMapper(); static ObjectMapper objectMapper = new ObjectMapper();
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf") @PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
@ -220,7 +224,7 @@ public class GetInfoOnPDF {
javascriptArray.add(jsNode); javascriptArray.add(jsNode);
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.error("exception", e);
} }
} }
} }
@ -253,7 +257,7 @@ public class GetInfoOnPDF {
} }
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); logger.error("exception", e);
} }
boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A"); boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A");
@ -305,7 +309,7 @@ public class GetInfoOnPDF {
new XmpSerializer().serialize(xmpMeta, os, true); new XmpSerializer().serialize(xmpMeta, os, true);
xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8); xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8);
} catch (XmpParsingException | IOException e) { } catch (XmpParsingException | IOException e) {
e.printStackTrace(); logger.error("exception", e);
} }
} }
@ -593,7 +597,7 @@ public class GetInfoOnPDF {
MediaType.APPLICATION_JSON); MediaType.APPLICATION_JSON);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); logger.error("exception", e);
} }
return null; return null;
} }
@ -691,7 +695,7 @@ public class GetInfoOnPDF {
Exception Exception
e) { // Catching general exception for brevity, ideally you'd catch specific e) { // Catching general exception for brevity, ideally you'd catch specific
// exceptions. // exceptions.
e.printStackTrace(); logger.error("exception", e);
} }
return false; return false;

View File

@ -117,7 +117,6 @@ public class PDFTableStripper extends PDFTextStripper {
/** /**
* Instantiate a new PDFTableStripper object. * Instantiate a new PDFTableStripper object.
* *
* @param document
* @throws IOException If there is an error loading the properties. * @throws IOException If there is an error loading the properties.
*/ */
public PDFTableStripper() throws IOException { public PDFTableStripper() throws IOException {

View File

@ -52,23 +52,23 @@ public class AccountWebController {
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2(); OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
if (oauth != null) { if (oauth != null) {
if (oauth.isSettingsValid()) { if (oauth.isSettingsValid()) {
providerList.put("oidc", "OpenID Connect"); providerList.put("oidc", oauth.getProvider());
} }
Client client = oauth.getClient(); Client client = oauth.getClient();
if (client != null) { if (client != null) {
GoogleProvider google = client.getGoogle(); GoogleProvider google = client.getGoogle();
if (google.isSettingsValid()) { if (google.isSettingsValid()) {
providerList.put("google", "Google"); providerList.put(google.getName(), google.getClientName());
} }
GithubProvider github = client.getGithub(); GithubProvider github = client.getGithub();
if (github.isSettingsValid()) { if (github.isSettingsValid()) {
providerList.put("github", "Github"); providerList.put(github.getName(), github.getClientName());
} }
KeycloakProvider keycloak = client.getKeycloak(); KeycloakProvider keycloak = client.getKeycloak();
if (keycloak.isSettingsValid()) { if (keycloak.isSettingsValid()) {
providerList.put("keycloak", "Keycloak"); providerList.put(keycloak.getName(), keycloak.getClientName());
} }
} }
} }
@ -262,8 +262,7 @@ public class AccountWebController {
userRepository.findByUsernameIgnoreCase( userRepository.findByUsernameIgnoreCase(
username); // Assuming findByUsername method exists username); // Assuming findByUsername method exists
if (!user.isPresent()) { if (!user.isPresent()) {
// Handle error appropriately return "redirect:/error";
return "redirect:/error"; // Example redirection in case of error
} }
// Convert settings map to JSON string // Convert settings map to JSON string
@ -273,8 +272,8 @@ public class AccountWebController {
settingsJson = objectMapper.writeValueAsString(user.get().getSettings()); settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
// Handle JSON conversion error // Handle JSON conversion error
e.printStackTrace(); logger.error("exception", e);
return "redirect:/error"; // Example redirection in case of error return "redirect:/error";
} }
String messageType = request.getParameter("messageType"); String messageType = request.getParameter("messageType");

View File

@ -15,6 +15,8 @@ import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
@ -33,6 +35,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
@Tag(name = "General", description = "General APIs") @Tag(name = "General", description = "General APIs")
public class GeneralWebController { public class GeneralWebController {
private static final Logger logger = LoggerFactory.getLogger(GeneralWebController.class);
@GetMapping("/pipeline") @GetMapping("/pipeline")
@Hidden @Hidden
public String pipelineForm(Model model) { public String pipelineForm(Model model) {
@ -74,7 +78,7 @@ public class GeneralWebController {
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.error("exception", e);
} }
} }
if (pipelineConfigsWithNames.size() == 0) { if (pipelineConfigsWithNames.size() == 0) {

View File

@ -6,6 +6,8 @@ import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@ -26,6 +28,8 @@ import stirling.software.SPDF.model.Dependency;
@Controller @Controller
public class HomeWebController { public class HomeWebController {
private static final Logger logger = LoggerFactory.getLogger(HomeWebController.class);
@GetMapping("/about") @GetMapping("/about")
@Hidden @Hidden
public String gameForm(Model model) { public String gameForm(Model model) {
@ -46,7 +50,7 @@ public class HomeWebController {
mapper.readValue(json, new TypeReference<Map<String, List<Dependency>>>() {}); mapper.readValue(json, new TypeReference<Map<String, List<Dependency>>>() {});
model.addAttribute("dependencies", data.get("dependencies")); model.addAttribute("dependencies", data.get("dependencies"));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.error("exception", e);
} }
return "licenses"; return "licenses";
} }

View File

@ -356,7 +356,7 @@ public class ApplicationProperties {
private KeycloakProvider keycloak = new KeycloakProvider(); private KeycloakProvider keycloak = new KeycloakProvider();
public Provider get(String registrationId) throws Exception { public Provider get(String registrationId) throws Exception {
switch (registrationId) { switch (registrationId.toLowerCase()) {
case "google": case "google":
return getGoogle(); return getGoogle();
case "github": case "github":
@ -455,6 +455,7 @@ public class ApplicationProperties {
@Override @Override
public Collection<String> getScopes() { public Collection<String> getScopes() {
if (scopes == null || scopes.isEmpty()) { if (scopes == null || scopes.isEmpty()) {
scopes = new ArrayList<>();
scopes.add("https://www.googleapis.com/auth/userinfo.email"); scopes.add("https://www.googleapis.com/auth/userinfo.email");
scopes.add("https://www.googleapis.com/auth/userinfo.profile"); scopes.add("https://www.googleapis.com/auth/userinfo.profile");
} }
@ -495,6 +496,11 @@ public class ApplicationProperties {
return "google"; return "google";
} }
@Override
public String getClientName() {
return "Google";
}
public boolean isSettingsValid() { public boolean isSettingsValid() {
return super.isValid(this.getClientId(), "clientId") return super.isValid(this.getClientId(), "clientId")
&& super.isValid(this.getClientSecret(), "clientSecret") && super.isValid(this.getClientSecret(), "clientSecret")
@ -555,6 +561,7 @@ public class ApplicationProperties {
public Collection<String> getScopes() { public Collection<String> getScopes() {
if (scopes == null || scopes.isEmpty()) { if (scopes == null || scopes.isEmpty()) {
scopes = new ArrayList<>();
scopes.add("read:user"); scopes.add("read:user");
} }
return scopes; return scopes;
@ -594,6 +601,11 @@ public class ApplicationProperties {
return "github"; return "github";
} }
@Override
public String getClientName() {
return "GitHub";
}
public boolean isSettingsValid() { public boolean isSettingsValid() {
return super.isValid(this.getClientId(), "clientId") return super.isValid(this.getClientId(), "clientId")
&& super.isValid(this.getClientSecret(), "clientSecret") && super.isValid(this.getClientSecret(), "clientSecret")
@ -642,7 +654,7 @@ public class ApplicationProperties {
@Override @Override
public Collection<String> getScopes() { public Collection<String> getScopes() {
if (scopes == null || scopes.isEmpty()) { if (scopes == null || scopes.isEmpty()) {
scopes.add("openid"); scopes = new ArrayList<>();
scopes.add("profile"); scopes.add("profile");
scopes.add("email"); scopes.add("email");
} }
@ -684,6 +696,11 @@ public class ApplicationProperties {
return "keycloak"; return "keycloak";
} }
@Override
public String getClientName() {
return "Keycloak";
}
public boolean isSettingsValid() { public boolean isSettingsValid() {
return isValid(this.getIssuer(), "issuer") return isValid(this.getIssuer(), "issuer")
&& isValid(this.getClientId(), "clientId") && isValid(this.getClientId(), "clientId")

View File

@ -0,0 +1,45 @@
package stirling.software.SPDF.model;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import org.thymeleaf.templateresource.ITemplateResource;
public class InputStreamTemplateResource implements ITemplateResource {
private InputStream inputStream;
private String characterEncoding;
public InputStreamTemplateResource(InputStream inputStream, String characterEncoding) {
this.inputStream = inputStream;
this.characterEncoding = characterEncoding;
}
@Override
public Reader reader() throws IOException {
return new InputStreamReader(inputStream, characterEncoding);
}
@Override
public ITemplateResource relative(String relativeLocation) {
// Implement logic for relative resources, if needed
throw new UnsupportedOperationException("Relative resources not supported");
}
@Override
public String getDescription() {
return "InputStream resource [Stream]";
}
@Override
public String getBaseName() {
return "streamResource";
}
@Override
public boolean exists() {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -4,11 +4,16 @@ import java.util.Collection;
public class Provider implements ProviderInterface { public class Provider implements ProviderInterface {
private String name; private String name;
private String clientName;
public String getName() { public String getName() {
return name; return name;
} }
public String getClientName() {
return clientName;
}
protected boolean isValid(String value, String name) { protected boolean isValid(String value, String name) {
if (value != null && !value.trim().isEmpty()) { if (value != null && !value.trim().isEmpty()) {
return true; return true;

View File

@ -5,6 +5,8 @@ import java.util.List;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
@ -19,6 +21,8 @@ import stirling.software.SPDF.utils.GeneralUtils;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class PDFWithPageNums extends PDFFile { public class PDFWithPageNums extends PDFFile {
private static final Logger logger = LoggerFactory.getLogger(PDFWithPageNums.class);
@Schema( @Schema(
description = description =
"The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')\"") "The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')\"")
@ -31,7 +35,7 @@ public class PDFWithPageNums extends PDFFile {
pageCount = Loader.loadPDF(getFileInput().getBytes()).getNumberOfPages(); pageCount = Loader.loadPDF(getFileInput().getBytes()).getNumberOfPages();
} catch (IOException e) { } catch (IOException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); logger.error("exception", e);
} }
return GeneralUtils.parsePageList(pageNumbers, pageCount, zeroCount); return GeneralUtils.parsePageList(pageNumbers, pageCount, zeroCount);
} }

View File

@ -53,7 +53,7 @@ public class FileToPdf {
command.add("--paper-size"); command.add("--paper-size");
command.add("a4"); command.add("a4");
if (request.getZoom() != 1.0) { if (request != null && request.getZoom() != 1.0) {
// Create a temporary CSS file // Create a temporary CSS file
File tempCssFile = Files.createTempFile("customStyle", ".css").toFile(); File tempCssFile = Files.createTempFile("customStyle", ".css").toFile();
try (FileWriter writer = new FileWriter(tempCssFile)) { try (FileWriter writer = new FileWriter(tempCssFile)) {

View File

@ -14,6 +14,8 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.fathzer.soft.javaluator.DoubleEvaluator; import com.fathzer.soft.javaluator.DoubleEvaluator;
@ -23,6 +25,8 @@ import io.github.pixee.security.Urls;
public class GeneralUtils { public class GeneralUtils {
private static final Logger logger = LoggerFactory.getLogger(GeneralUtils.class);
public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException { public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
File tempFile = Files.createTempFile("temp", null).toFile(); File tempFile = Files.createTempFile("temp", null).toFile();
try (FileOutputStream os = new FileOutputStream(tempFile)) { try (FileOutputStream os = new FileOutputStream(tempFile)) {
@ -234,7 +238,7 @@ public class GeneralUtils {
try { try {
Files.createDirectories(folder); Files.createDirectories(folder);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.error("exception", e);
return false; return false;
} }
} }

View File

@ -14,6 +14,8 @@ import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -24,6 +26,7 @@ import io.github.pixee.security.Filenames;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
public class PDFToFile { public class PDFToFile {
private static final Logger logger = LoggerFactory.getLogger(PDFToFile.class);
public ResponseEntity<byte[]> processPdfToHtml(MultipartFile inputFile) public ResponseEntity<byte[]> processPdfToHtml(MultipartFile inputFile)
throws IOException, InterruptedException { throws IOException, InterruptedException {
@ -67,18 +70,20 @@ public class PDFToFile {
// Return output files in a ZIP archive // Return output files in a ZIP archive
fileName = pdfBaseName + "ToHtml.zip"; fileName = pdfBaseName + "ToHtml.zip";
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream); try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
for (File outputFile : outputFiles) {
for (File outputFile : outputFiles) { ZipEntry entry = new ZipEntry(outputFile.getName());
ZipEntry entry = new ZipEntry(outputFile.getName()); zipOutputStream.putNextEntry(entry);
zipOutputStream.putNextEntry(entry); try (FileInputStream fis = new FileInputStream(outputFile)) {
FileInputStream fis = new FileInputStream(outputFile); IOUtils.copy(fis, zipOutputStream);
IOUtils.copy(fis, zipOutputStream); } catch (IOException e) {
fis.close(); logger.error("Exception writing zip entry", e);
zipOutputStream.closeEntry(); }
zipOutputStream.closeEntry();
}
} catch (IOException e) {
logger.error("Exception writing zip", e);
} }
zipOutputStream.close();
fileBytes = byteArrayOutputStream.toByteArray(); fileBytes = byteArrayOutputStream.toByteArray();
} finally { } finally {
@ -160,18 +165,22 @@ public class PDFToFile {
// Return output files in a ZIP archive // Return output files in a ZIP archive
fileName = pdfBaseName + "To" + outputFormat + ".zip"; fileName = pdfBaseName + "To" + outputFormat + ".zip";
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream); try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
for (File outputFile : outputFiles) {
ZipEntry entry = new ZipEntry(outputFile.getName());
zipOutputStream.putNextEntry(entry);
try (FileInputStream fis = new FileInputStream(outputFile)) {
IOUtils.copy(fis, zipOutputStream);
} catch (IOException e) {
logger.error("Exception writing zip entry", e);
}
for (File outputFile : outputFiles) { zipOutputStream.closeEntry();
ZipEntry entry = new ZipEntry(outputFile.getName()); }
zipOutputStream.putNextEntry(entry); } catch (IOException e) {
FileInputStream fis = new FileInputStream(outputFile); logger.error("Exception writing zip", e);
IOUtils.copy(fis, zipOutputStream);
fis.close();
zipOutputStream.closeEntry();
} }
zipOutputStream.close();
fileBytes = byteArrayOutputStream.toByteArray(); fileBytes = byteArrayOutputStream.toByteArray();
} }

View File

@ -125,7 +125,7 @@ public class ProcessExecutor {
logger.warn( logger.warn(
"Error reader thread was interrupted due to timeout."); "Error reader thread was interrupted due to timeout.");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.error("exception", e);
} }
}); });
@ -149,7 +149,7 @@ public class ProcessExecutor {
logger.warn( logger.warn(
"Error reader thread was interrupted due to timeout."); "Error reader thread was interrupted due to timeout.");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.error("exception", e);
} }
}); });

View File

@ -26,7 +26,7 @@ bored=Nudíte se při čekání?
alphabet=Abeceda alphabet=Abeceda
downloadPdf=Stáhnout PDF downloadPdf=Stáhnout PDF
text=Text text=Text
font=Font font=Písmo
selectFillter=-- Vyberte -- selectFillter=-- Vyberte --
pageNum=Číslo stránky pageNum=Číslo stránky
sizes.small=Malé sizes.small=Malé

View File

@ -71,7 +71,7 @@ visitGithub=GitHub-Repository besuchen
donate=Spenden donate=Spenden
color=Farbe color=Farbe
sponsor=Sponsor sponsor=Sponsor
info=Die Info info=Informationen
@ -660,10 +660,10 @@ certSign.submit=PDF signieren
#removeCertSign #removeCertSign
removeCertSign.title=Remove Certificate Signature removeCertSign.title=Zertifikatsignatur entfernen
removeCertSign.header=Remove the digital certificate from the PDF removeCertSign.header=Digitales Zertifikat aus dem PDF entfernen
removeCertSign.selectPDF=Select a PDF file: removeCertSign.selectPDF=PDF-Datei auswählen:
removeCertSign.submit=Remove Signature removeCertSign.submit=Signatur entfernen
#removeBlanks #removeBlanks

View File

@ -1,7 +1,7 @@
########### ###########
# Generic # # Generic #
########### ###########
# the direction that the language is written (ltr=left to right, rtl = right to left) # the direction that the language is written (ltr = left to right, rtl = right to left)
language.direction=ltr language.direction=ltr
pdfPrompt=Odaberi PDF(ove) pdfPrompt=Odaberi PDF(ove)
@ -331,9 +331,10 @@ compare.tags=razlikovati,kontrast,izmjene,analiza
home.certSign.title=Potpišite s certifikatom home.certSign.title=Potpišite s certifikatom
home.certSign.desc=Potpisuje PDF s certifikatom/ključem (PEM/P12) home.certSign.desc=Potpisuje PDF s certifikatom/ključem (PEM/P12)
certSign.tags=autentifikacija,PEM,P12,zvanično,šifriranje certSign.tags=autentifikacija,PEM,P12,zvanično,šifriranje
# home.removeCertSign.title=Remove Certificate Sign
# home.removeCertSign.desc=Remove certificate signature from PDF home.removeCertSign.title=Remove Certificate Sign
# removeCertSign.tags=authenticate,PEM,P12,official,decrypt home.removeCertSign.desc=Remove certificate signature from PDF
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
home.pageLayout.title=Izgled s više stranica home.pageLayout.title=Izgled s više stranica
home.pageLayout.desc=Spojite više stranica PDF dokumenta u jednu stranicu home.pageLayout.desc=Spojite više stranica PDF dokumenta u jednu stranicu
@ -656,10 +657,13 @@ certSign.reason=Razlog
certSign.location=Mjesto certSign.location=Mjesto
certSign.name=Ime certSign.name=Ime
certSign.submit=Potpiši PDF certSign.submit=Potpiši PDF
# removeCertSign.title=Remove Certificate Signature
# removeCertSign.header=Remove the digital certificate from the PDF
# removeCertSign.selectPDF=Select a PDF file: #removeCertSign
# removeCertSign.submit=Remove Signature removeCertSign.title=Remove Certificate Signature
removeCertSign.header=Remove the digital certificate from the PDF
removeCertSign.selectPDF=Select a PDF file:
removeCertSign.submit=Remove Signature
#removeBlanks #removeBlanks

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-no" viewBox="0 0 640 480">
<path fill="#ed2939" d="M0 0h640v480H0z"/>
<path fill="#fff" d="M180 0h120v480H180z"/>
<path fill="#fff" d="M0 180h640v120H0z"/>
<path fill="#002664" d="M210 0h60v480h-60z"/>
<path fill="#002664" d="M0 210h640v60H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View File

@ -34,7 +34,7 @@
<td th:text="#{${user.roleName}}"></td> <td th:text="#{${user.roleName}}"></td>
<td> <td>
<form th:if="${user.username != currentUsername}" th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post"> <form th:if="${user.username != currentUsername}" th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post">
<button type="submit" th:text="#{delete}">Delete</button> <button class="btn btn-danger" type="submit" th:text="#{delete}">Delete</button>
</form> </form>
</td> </td>
<td th:text="${user.authenticationType}"></td> <td th:text="${user.authenticationType}"></td>
@ -47,7 +47,7 @@
<span th:text="#{${addMessage}}">Default message if not found</span> <span th:text="#{${addMessage}}">Default message if not found</span>
</div> </div>
<button class="btn btn-outline-info" data-toggle="tooltip" data-placement="auto" th:title="#{adminUserSettings.usernameInfo}" th:text="#{help}">Help</button> <button class="btn btn-outline-info" data-toggle="tooltip" data-placement="auto" th:title="#{adminUserSettings.usernameInfo}" th:text="#{help}">Help</button>
<form id="formsaveuser" action="/api/v1/user/admin/saveUser" method="post"> <form id="formsaveuser" th:action="@{/api/v1/user/admin/saveUser}" method="post">
<div class="mb-3"> <div class="mb-3">
<label for="username" th:text="#{username}">Username</label> <label for="username" th:text="#{username}">Username</label>
<input type="text" class="form-control" name="username" id="username" th:title="#{adminUserSettings.usernameInfo}" required> <input type="text" class="form-control" name="username" id="username" th:title="#{adminUserSettings.usernameInfo}" required>
@ -78,7 +78,7 @@
<div th:if="${changeMessage}" class="alert alert-danger"> <div th:if="${changeMessage}" class="alert alert-danger">
<span th:text="#{${changeMessage}}">Default message if not found</span> <span th:text="#{${changeMessage}}">Default message if not found</span>
</div> </div>
<form action="/api/v1/user/admin/changeRole" method="post"> <form th:action="@{/api/v1/user/admin/changeRole}" method="post">
<div class="mb-3"> <div class="mb-3">
<label for="username" th:text="#{username}">Username</label> <label for="username" th:text="#{username}">Username</label>
<select name="username" class="form-control" required> <select name="username" class="form-control" required>

View File

@ -16,7 +16,7 @@
<span class="material-symbols-rounded tool-header-icon organize">crop</span> <span class="material-symbols-rounded tool-header-icon organize">crop</span>
<span class="tool-header-text" th:text="#{crop.header}"></span> <span class="tool-header-text" th:text="#{crop.header}"></span>
</div> </div>
<form id="cropForm" action="/api/v1/general/crop" method="post" enctype="multipart/form-data"> <form id="cropForm" th:action="@{/api/v1/general/crop}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<input id="x" type="hidden" name="x"> <input id="x" type="hidden" name="x">
<input id="y" type="hidden" name="y"> <input id="y" type="hidden" name="y">

View File

@ -21,7 +21,7 @@
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" class="btn btn-primary" target="_blank" th:text="#{error.github}"></a> <a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" class="btn btn-primary" target="_blank" th:text="#{error.github}"></a>
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" class="btn btn-primary" target="_blank" th:text="#{joinDiscord}"></a> <a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" class="btn btn-primary" target="_blank" th:text="#{joinDiscord}"></a>
</div> </div>
<a href="/" id="home-button" class="home-button btn btn-primary" th:text="#{goHomepage}"></a> <a th:href="@{/}" id="home-button" class="home-button btn btn-primary" th:text="#{goHomepage}"></a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -30,4 +30,5 @@
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="uk_UA"> <img src="images/flags/ua.svg" alt="icon" width="20" height="15"> Українська</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="uk_UA"> <img src="images/flags/ua.svg" alt="icon" width="20" height="15"> Українська</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="cs_CZ"> <img src="images/flags/cz.svg" alt="icon" width="20" height="15"> Česky</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="cs_CZ"> <img src="images/flags/cz.svg" alt="icon" width="20" height="15"> Česky</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hr_HR"> <img src="images/flags/hr.svg" alt="icon" width="20" height="15"> Hrvatski</a> <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hr_HR"> <img src="images/flags/hr.svg" alt="icon" width="20" height="15"> Hrvatski</a>
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="no_NB"> <img src="images/flags/no.svg" alt="icon" width="20" height="15"> Norsk</a>
</th:block> </th:block>

View File

@ -8,7 +8,7 @@
<script src="js/githubVersion.js"></script> <script src="js/githubVersion.js"></script>
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container "> <div class="container ">
<a class="navbar-brand" href="/" style="display: flex;"> <a class="navbar-brand" th:href="@{/}" style="display: flex;">
<img class="main-icon" src="favicon.svg" alt="icon"> <img class="main-icon" src="favicon.svg" alt="icon">
<span class="icon-text" th:text="${@navBarText}"></span> <span class="icon-text" th:text="${@navBarText}"></span>
</a> </a>

View File

@ -19,7 +19,7 @@
</div> </div>
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/flatten}" id="pdfForm" class="mb-3"> <form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/flatten}" id="pdfForm" class="mb-3">
<div class="custom-file"> <div class="custom-file">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div> <div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="flattenOnlyForms" name="flattenOnlyForms"> <input type="checkbox" id="flattenOnlyForms" name="flattenOnlyForms">
@ -35,4 +35,4 @@
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -298,7 +298,7 @@ See https://github.com/adobe-type-tools/cmap-resources
<input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="15" data-l10n-id="pdfjs-page-input" autocomplete="off"> <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="15" data-l10n-id="pdfjs-page-input" autocomplete="off">
</span> </span>
<span id="numPages" class="toolbarLabel"></span> <span id="numPages" class="toolbarLabel"></span>
<a class="navbar-brand hiddenMediumView" href="/" tabindex="16" > <a class="navbar-brand hiddenMediumView" th:href="@{/}" tabindex="16" >
<img class="main-icon" src="favicon.svg" alt="icon" style="max-height: 1.6rem; width: auto;"> <img class="main-icon" src="favicon.svg" alt="icon" style="max-height: 1.6rem; width: auto;">
<span class="icon-text" style="color: #ffffff;" th:text="${@appName}">Stirling PDF</span> <span class="icon-text" style="color: #ffffff;" th:text="${@appName}">Stirling PDF</span>
</a> </a>
@ -308,7 +308,7 @@ See https://github.com/adobe-type-tools/cmap-resources
<button id="editorHighlight" class="toolbarButton" hidden="true" disabled="disabled" title="Highlight" role="radio" aria-checked="false" aria-controls="editorHighlightParamsToolbar" tabindex="31" data-l10n-id="pdfjs-editor-highlight-button"> <button id="editorHighlight" class="toolbarButton" hidden="true" disabled="disabled" title="Highlight" role="radio" aria-checked="false" aria-controls="editorHighlightParamsToolbar" tabindex="31" data-l10n-id="pdfjs-editor-highlight-button">
<span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span> <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
</button> </button>
<a id="backToHome" class="toolbarButton hiddenMediumView" title="Back to Main Page" role="radio" aria-checked="false" tabindex="32" href="/"> <a id="backToHome" class="toolbarButton hiddenMediumView" title="Back to Main Page" role="radio" aria-checked="false" tabindex="32" th:href="@{/}">
<span data-l10n-id="pdfjs-open-file-button-label">Back to Main Page</span> <span data-l10n-id="pdfjs-open-file-button-label">Back to Main Page</span>
</a> </a>
<button id="openFile" class="toolbarButton hiddenMediumView" title="Open File" role="radio" aria-checked="false" tabindex="33" data-l10n-id="pdfjs-open-file-button"> <button id="openFile" class="toolbarButton hiddenMediumView" title="Open File" role="radio" aria-checked="false" tabindex="33" data-l10n-id="pdfjs-open-file-button">