Compare commits
71 Commits
v0.29.0
...
Frooodle/l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8709c25c0 | ||
|
|
261e69f3ca | ||
|
|
addb999301 | ||
|
|
496fcad698 | ||
|
|
daf4f49050 | ||
|
|
2cdad2f9ff | ||
|
|
4c4613cfec | ||
|
|
8aa0e85c20 | ||
|
|
1129487aaf | ||
|
|
378d0f8afb | ||
|
|
a12de218c0 | ||
|
|
1ba271b0f4 | ||
|
|
6ccc29d0a7 | ||
|
|
eee2f5c666 | ||
|
|
e660237e28 | ||
|
|
83e93688ee | ||
|
|
ceeecc37ab | ||
|
|
fec717484f | ||
|
|
85e1716aa2 | ||
|
|
dedfabd630 | ||
|
|
3b5b7772a9 | ||
|
|
2a6b4ca87f | ||
|
|
e325943f16 | ||
|
|
48aae48f0e | ||
|
|
494bc2c09f | ||
|
|
45e4c15d2d | ||
|
|
22a58ad0c3 | ||
|
|
c59d3ff3e0 | ||
|
|
bb37ba1f30 | ||
|
|
5832147b30 | ||
|
|
092b4cc5cb | ||
|
|
86bb37aa7a | ||
|
|
20dc2f60cd | ||
|
|
da988e8127 | ||
|
|
b8115531e2 | ||
|
|
9b96367496 | ||
|
|
3ded6de576 | ||
|
|
1c6e5df77d | ||
|
|
df901db1f8 | ||
|
|
b46ccdde44 | ||
|
|
fde1f626eb | ||
|
|
f47ed3b42e | ||
|
|
a81856d83b | ||
|
|
4fa1b4adb0 | ||
|
|
03158b05e4 | ||
|
|
28c55ca80c | ||
|
|
d6e9e8b20b | ||
|
|
0f43062cc1 | ||
|
|
f72e5c8bca | ||
|
|
936f36f171 | ||
|
|
dab230e3f8 | ||
|
|
0d3ac8bebe | ||
|
|
6ca14edaf1 | ||
|
|
f9677b1fe8 | ||
|
|
04a6ebf515 | ||
|
|
262e2ed47a | ||
|
|
4436759e12 | ||
|
|
6e1a5d2ea0 | ||
|
|
35490f6ff7 | ||
|
|
9f63b0b115 | ||
|
|
c2a8771c66 | ||
|
|
f87801323c | ||
|
|
bed6227bbe | ||
|
|
24f99fce31 | ||
|
|
ba2311b3e5 | ||
|
|
688e01d70d | ||
|
|
0014560a96 | ||
|
|
cbf1c3a59b | ||
|
|
b13b925bf0 | ||
|
|
87925ac618 | ||
|
|
c6c33d611a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -110,7 +110,6 @@ watchedFolders/
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
*.db
|
||||
|
||||
@@ -15,6 +15,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||
# Copy necessary files
|
||||
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||
COPY pipeline /pipeline
|
||||
COPY build/libs/*.jar app.jar
|
||||
|
||||
@@ -33,11 +34,11 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
su-exec \
|
||||
openjdk21-jre && \
|
||||
# User permissions
|
||||
mkdir /configs /logs /customFiles && \
|
||||
mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto && \
|
||||
chmod +x /scripts/*.sh && \
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
|
||||
# Set environment variables
|
||||
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||
|
||||
@@ -257,9 +257,11 @@ To override the default configuration, you can add the following to `/.git/Stirl
|
||||
|
||||
```bash
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
host: 0.0.0.0 # Not working - use instead address
|
||||
address: 0.0.0.0
|
||||
port: 3000
|
||||
```
|
||||
'-Djava.net.preferIPv4Stack=true' --> To force ipv4 only in the java starting command
|
||||
|
||||
**Note:** This file is created after the first application launch. To have it before that, you can create the directory and add the file yourself.
|
||||
|
||||
|
||||
66
README.md
66
README.md
@@ -172,42 +172,42 @@ Stirling PDF currently supports 38!
|
||||
|
||||
| Language | Progress |
|
||||
| ------------------------------------------- | -------------------------------------- |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| English (English) (en_GB) |  |
|
||||
| English (US) (en_US) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
|
||||
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||
|
||||
|
||||
25
build.gradle
25
build.gradle
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "org.springframework.boot" version "3.3.3"
|
||||
id "org.springframework.boot" version "3.3.4"
|
||||
id "io.spring.dependency-management" version "1.1.6"
|
||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||
id "io.swagger.swaggerhub" version "1.3.2"
|
||||
@@ -13,7 +13,7 @@ plugins {
|
||||
import com.github.jk1.license.render.*
|
||||
|
||||
ext {
|
||||
springBootVersion = "3.3.3"
|
||||
springBootVersion = "3.3.4"
|
||||
pdfboxVersion = "3.0.3"
|
||||
logbackVersion = "1.5.7"
|
||||
imageioVersion = "3.11.0"
|
||||
@@ -22,7 +22,7 @@ ext {
|
||||
}
|
||||
|
||||
group = "stirling.software"
|
||||
version = "0.29.0"
|
||||
version = "0.30.0"
|
||||
|
||||
java {
|
||||
// 17 is lowest but we support and recommend 21
|
||||
@@ -32,6 +32,12 @@ java {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
maven {
|
||||
url "https://build.shibboleth.net/nexus/content/repositories/releases/"
|
||||
}
|
||||
maven {
|
||||
url "https://build.shibboleth.net/maven/releases/"
|
||||
}
|
||||
}
|
||||
|
||||
licenseReport {
|
||||
@@ -115,7 +121,7 @@ configurations.all {
|
||||
}
|
||||
dependencies {
|
||||
//security updates
|
||||
implementation "org.springframework:spring-webmvc:6.1.9"
|
||||
implementation "org.springframework:spring-webmvc:6.1.13"
|
||||
|
||||
implementation("io.github.pixee:java-security-toolkit:1.2.0")
|
||||
|
||||
@@ -127,6 +133,9 @@ dependencies {
|
||||
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
|
||||
|
||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
||||
implementation 'com.posthog.java:posthog:1.1.1'
|
||||
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
||||
|
||||
|
||||
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
||||
@@ -134,6 +143,8 @@ dependencies {
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||
|
||||
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.3.3'
|
||||
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
||||
//2.2.x requires rebuild of DB file.. need migration path
|
||||
runtimeOnly "com.h2database:h2:2.1.214"
|
||||
// implementation "com.h2database:h2:2.2.224"
|
||||
@@ -162,7 +173,7 @@ dependencies {
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion"
|
||||
|
||||
implementation "commons-io:commons-io:2.16.1"
|
||||
implementation "commons-io:commons-io:2.17.0"
|
||||
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
||||
//general PDF
|
||||
|
||||
@@ -187,8 +198,8 @@ dependencies {
|
||||
implementation "io.micrometer:micrometer-core:1.13.4"
|
||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation "org.commonmark:commonmark:0.22.0"
|
||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0"
|
||||
implementation "org.commonmark:commonmark:0.23.0"
|
||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.23.0"
|
||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||
implementation "com.fathzer:javaluator:3.0.5"
|
||||
|
||||
11
cucumber/exampleFiles/example.html
Normal file
11
cucumber/exampleFiles/example.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h1>My First Heading</h1>
|
||||
|
||||
<p>My first paragraph.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
16
cucumber/exampleFiles/example.md
Normal file
16
cucumber/exampleFiles/example.md
Normal file
@@ -0,0 +1,16 @@
|
||||
header
|
||||
============
|
||||
|
||||
Header2
|
||||
------------
|
||||
text
|
||||
|
||||
text2
|
||||
|
||||
## **PDF Features**
|
||||
|
||||
### **Page Operations**
|
||||
|
||||
- View and modify PDFs - View multi page PDFs with custom viewing sorting and searching. Plus on page edit features like annotate, draw and adding text and images. (Using PDF.js with Joxit and Liberation.Liberation fonts)
|
||||
- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages.
|
||||
- Merge multiple PDFs together into a single resultant file.
|
||||
BIN
cucumber/exampleFiles/example_html.zip
Normal file
BIN
cucumber/exampleFiles/example_html.zip
Normal file
Binary file not shown.
@@ -123,7 +123,7 @@ Feature: API Validation
|
||||
| odt | .odt |
|
||||
| doc | .doc |
|
||||
|
||||
@ocr
|
||||
@ocr @pdfa1
|
||||
Scenario: PDFA
|
||||
Given I use an example file at "exampleFiles/pdfa2.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
@@ -134,7 +134,7 @@ Feature: API Validation
|
||||
And the response file should have extension ".pdf"
|
||||
And the response file should have size greater than 100
|
||||
|
||||
@ocr
|
||||
@ocr @pdfa2
|
||||
Scenario: PDFA1
|
||||
Given I use an example file at "exampleFiles/pdfa1.pdf" as parameter "fileInput"
|
||||
And the request data includes
|
||||
@@ -218,6 +218,28 @@ Feature: API Validation
|
||||
| .odt |
|
||||
| .pptx |
|
||||
| .rtf |
|
||||
|
||||
|
||||
|
||||
@calibre @positive @htmltopdf
|
||||
Scenario: Convert HTML to PDF
|
||||
Given I use an example file at "exampleFiles/example.html" as parameter "fileInput"
|
||||
When I send the API request to the endpoint "/api/v1/convert/html/pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have size greater than 100
|
||||
And the response file should have extension ".pdf"
|
||||
|
||||
@calibre @positive @zippedhtmltopdf
|
||||
Scenario: Convert zipped HTML to PDF
|
||||
Given I use an example file at "exampleFiles/example_html.zip" as parameter "fileInput"
|
||||
When I send the API request to the endpoint "/api/v1/convert/html/pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have size greater than 100
|
||||
And the response file should have extension ".pdf"
|
||||
|
||||
@calibre @positive @markdowntopdf
|
||||
Scenario: Convert Markdown to PDF
|
||||
Given I use an example file at "exampleFiles/example.md" as parameter "fileInput"
|
||||
When I send the API request to the endpoint "/api/v1/convert/markdown/pdf"
|
||||
Then the response status code should be 200
|
||||
And the response file should have size greater than 100
|
||||
And the response file should have extension ".pdf"
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
@@ -19,7 +19,7 @@ services:
|
||||
- /stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "true"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
|
||||
10
scripts/replace_translation_line.sh
Normal file
10
scripts/replace_translation_line.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
translation_key="pdfToPDFA.credit"
|
||||
old_value="OCRmyPDF"
|
||||
new_value="ghostscript"
|
||||
|
||||
for file in ../src/main/resources/messages_*.properties; do
|
||||
sed -i "/^$translation_key=/s/$old_value/$new_value/" "$file"
|
||||
echo "Updated $file"
|
||||
done
|
||||
@@ -17,9 +17,10 @@ public class EEAppConfig {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@Bean(name = "RunningEE")
|
||||
@Autowired private LicenseKeyChecker licenseKeyChecker;
|
||||
|
||||
@Bean(name = "runningEE")
|
||||
public boolean runningEnterpriseEdition() {
|
||||
// TODO: Implement EE detection
|
||||
return false;
|
||||
return licenseKeyChecker.getEnterpriseEnabledResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
package stirling.software.SPDF.EE;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.posthog.java.shaded.org.json.JSONObject;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class KeygenLicenseVerifier {
|
||||
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
|
||||
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired
|
||||
public KeygenLicenseVerifier(ApplicationProperties applicationProperties) {
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
public boolean verifyLicense(String licenseKey) {
|
||||
try {
|
||||
log.info("Checking license key");
|
||||
String machineFingerprint = generateMachineFingerprint();
|
||||
|
||||
// First, try to validate the license
|
||||
JsonNode validationResponse = validateLicense(licenseKey, machineFingerprint);
|
||||
if (validationResponse != null) {
|
||||
boolean isValid = validationResponse.path("meta").path("valid").asBoolean();
|
||||
String licenseId = validationResponse.path("data").path("id").asText();
|
||||
if (!isValid) {
|
||||
String code = validationResponse.path("meta").path("code").asText();
|
||||
log.debug(code);
|
||||
if ("NO_MACHINE".equals(code)
|
||||
|| "NO_MACHINES".equals(code)
|
||||
|| "FINGERPRINT_SCOPE_MISMATCH".equals(code)) {
|
||||
log.info(
|
||||
"License not activated for this machine. Attempting to activate...");
|
||||
boolean activated =
|
||||
activateMachine(licenseKey, licenseId, machineFingerprint);
|
||||
if (activated) {
|
||||
// Revalidate after activation
|
||||
validationResponse = validateLicense(licenseKey, machineFingerprint);
|
||||
isValid =
|
||||
validationResponse != null
|
||||
&& validationResponse
|
||||
.path("meta")
|
||||
.path("valid")
|
||||
.asBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("Error verifying license: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode validateLicense(String licenseKey, String machineFingerprint)
|
||||
throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
String requestBody =
|
||||
String.format(
|
||||
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
|
||||
licenseKey, machineFingerprint);
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder()
|
||||
.uri(
|
||||
URI.create(
|
||||
BASE_URL
|
||||
+ "/"
|
||||
+ ACCOUNT_ID
|
||||
+ "/licenses/actions/validate-key"))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
// .header("Authorization", "License " + licenseKey)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
log.info(" validateLicenseResponse body: " + response.body());
|
||||
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||
if (response.statusCode() == 200) {
|
||||
|
||||
JsonNode metaNode = jsonResponse.path("meta");
|
||||
boolean isValid = metaNode.path("valid").asBoolean();
|
||||
|
||||
String detail = metaNode.path("detail").asText();
|
||||
String code = metaNode.path("code").asText();
|
||||
|
||||
log.debug("License validity: " + isValid);
|
||||
log.debug("Validation detail: " + detail);
|
||||
log.debug("Validation code: " + code);
|
||||
|
||||
int users =
|
||||
jsonResponse
|
||||
.path("data")
|
||||
.path("attributes")
|
||||
.path("metadata")
|
||||
.path("users")
|
||||
.asInt(0);
|
||||
applicationProperties.getEnterpriseEdition().setMaxUsers(users);
|
||||
log.info(applicationProperties.toString());
|
||||
|
||||
} else {
|
||||
log.error("Error validating license. Status code: " + response.statusCode());
|
||||
}
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
private boolean activateMachine(String licenseKey, String licenseId, String machineFingerprint)
|
||||
throws Exception {
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
String hostname;
|
||||
try {
|
||||
hostname = java.net.InetAddress.getLocalHost().getHostName();
|
||||
} catch (Exception e) {
|
||||
hostname = "Unknown";
|
||||
}
|
||||
|
||||
JSONObject body =
|
||||
new JSONObject()
|
||||
.put(
|
||||
"data",
|
||||
new JSONObject()
|
||||
.put("type", "machines")
|
||||
.put(
|
||||
"attributes",
|
||||
new JSONObject()
|
||||
.put("fingerprint", machineFingerprint)
|
||||
.put(
|
||||
"platform",
|
||||
System.getProperty(
|
||||
"os.name")) // Added
|
||||
// platform
|
||||
// parameter
|
||||
.put(
|
||||
"name",
|
||||
hostname)) // Added name parameter
|
||||
.put(
|
||||
"relationships",
|
||||
new JSONObject()
|
||||
.put(
|
||||
"license",
|
||||
new JSONObject()
|
||||
.put(
|
||||
"data",
|
||||
new JSONObject()
|
||||
.put(
|
||||
"type",
|
||||
"licenses")
|
||||
.put(
|
||||
"id",
|
||||
licenseId)))));
|
||||
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder()
|
||||
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines"))
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header(
|
||||
"Authorization",
|
||||
"License " + licenseKey) // Keep the license key authentication
|
||||
.POST(
|
||||
HttpRequest.BodyPublishers.ofString(
|
||||
body.toString())) // Send the JSON body
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
log.debug("activateMachine Response body: " + response.body());
|
||||
if (response.statusCode() == 201) {
|
||||
log.info("Machine activated successfully");
|
||||
return true;
|
||||
} else {
|
||||
log.error(
|
||||
"Error activating machine. Status code: {}, error: {}",
|
||||
response.statusCode(),
|
||||
response.body());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String generateMachineFingerprint() {
|
||||
return GeneralUtils.generateMachineFingerprint();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package stirling.software.SPDF.EE;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LicenseKeyChecker {
|
||||
|
||||
private final KeygenLicenseVerifier licenseService;
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
private boolean enterpriseEnbaledResult = false;
|
||||
|
||||
@Autowired
|
||||
public LicenseKeyChecker(
|
||||
KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {
|
||||
this.licenseService = licenseService;
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 604800000, initialDelay = 1000) // 7 days in milliseconds
|
||||
public void checkLicensePeriodically() {
|
||||
checkLicense();
|
||||
}
|
||||
|
||||
private void checkLicense() {
|
||||
if (!applicationProperties.getEnterpriseEdition().isEnabled()) {
|
||||
enterpriseEnbaledResult = false;
|
||||
} else {
|
||||
enterpriseEnbaledResult =
|
||||
licenseService.verifyLicense(
|
||||
applicationProperties.getEnterpriseEdition().getKey());
|
||||
if (enterpriseEnbaledResult) {
|
||||
log.info("License key is valid.");
|
||||
} else {
|
||||
log.info("License key is invalid.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateLicenseKey(String newKey) throws IOException {
|
||||
applicationProperties.getEnterpriseEdition().setKey(newKey);
|
||||
GeneralUtils.saveKeyToConfig("EnterpriseEdition.key", newKey, false);
|
||||
checkLicense();
|
||||
}
|
||||
|
||||
public boolean getEnterpriseEnabledResult() {
|
||||
return enterpriseEnbaledResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package stirling.software.SPDF.Factories;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
import stirling.software.SPDF.utils.misc.CustomColorReplaceStrategy;
|
||||
import stirling.software.SPDF.utils.misc.InvertFullColorStrategy;
|
||||
import stirling.software.SPDF.utils.misc.ReplaceAndInvertColorStrategy;
|
||||
|
||||
@Component
|
||||
public class ReplaceAndInvertColorFactory {
|
||||
|
||||
public ReplaceAndInvertColorStrategy replaceAndInvert(
|
||||
MultipartFile file,
|
||||
ReplaceAndInvert replaceAndInvertOption,
|
||||
HighContrastColorCombination highContrastColorCombination,
|
||||
String backGroundColor,
|
||||
String textColor) {
|
||||
|
||||
if (replaceAndInvertOption == ReplaceAndInvert.CUSTOM_COLOR
|
||||
|| replaceAndInvertOption == ReplaceAndInvert.HIGH_CONTRAST_COLOR) {
|
||||
|
||||
return new CustomColorReplaceStrategy(
|
||||
file,
|
||||
replaceAndInvertOption,
|
||||
textColor,
|
||||
backGroundColor,
|
||||
highContrastColorCombination);
|
||||
|
||||
} else if (replaceAndInvertOption == ReplaceAndInvert.FULL_INVERSION) {
|
||||
|
||||
return new InvertFullColorStrategy(file, replaceAndInvertOption);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.pixee.security.SystemCommand;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class LibreOfficeListener {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class);
|
||||
@@ -31,7 +34,7 @@ public class LibreOfficeListener {
|
||||
private LibreOfficeListener() {}
|
||||
|
||||
private boolean isListenerRunning() {
|
||||
System.out.println("waiting for listener to start");
|
||||
log.info("waiting for listener to start");
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.connect(
|
||||
new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package stirling.software.SPDF;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -30,14 +31,36 @@ public class SPdfApplication {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
||||
|
||||
@Autowired private Environment env;
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
private static String serverPortStatic;
|
||||
|
||||
@Value("${server.port:8080}")
|
||||
public void setServerPortStatic(String port) {
|
||||
SPdfApplication.serverPortStatic = port;
|
||||
if (port.equalsIgnoreCase("auto")) {
|
||||
// Use Spring Boot's automatic port assignment (server.port=0)
|
||||
SPdfApplication.serverPortStatic =
|
||||
"0"; // This will let Spring Boot assign an available port
|
||||
} else {
|
||||
SPdfApplication.serverPortStatic = port;
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
|
||||
private static String findAvailablePort(int startPort) {
|
||||
int port = startPort;
|
||||
while (!isPortAvailable(port)) {
|
||||
port++;
|
||||
}
|
||||
return String.valueOf(port);
|
||||
}
|
||||
|
||||
private static boolean isPortAvailable(int port) {
|
||||
try (ServerSocket socket = new ServerSocket(port)) {
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
@@ -47,13 +70,17 @@ public class SPdfApplication {
|
||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||
if (browserOpen) {
|
||||
try {
|
||||
String url = "http://localhost:" + getNonStaticPort();
|
||||
String url = "http://localhost:" + getStaticPort();
|
||||
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
if (os.contains("win")) {
|
||||
// For Windows
|
||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
||||
} else if (os.contains("mac")) {
|
||||
rt.exec("open " + url);
|
||||
} else if (os.contains("nix") || os.contains("nux")) {
|
||||
rt.exec("xdg-open " + url);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error opening browser: {}", e.getMessage());
|
||||
@@ -69,15 +96,13 @@ public class SPdfApplication {
|
||||
app.addInitializers(new ConfigInitializer());
|
||||
Map<String, String> propertyFiles = new HashMap<>();
|
||||
|
||||
// stirling pdf settings file
|
||||
// External config files
|
||||
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
||||
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
||||
} else {
|
||||
logger.warn(
|
||||
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
|
||||
logger.warn("External configuration file 'configs/settings.yml' does not exist.");
|
||||
}
|
||||
|
||||
// custom javs settings file
|
||||
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
||||
String existingLocation =
|
||||
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||
@@ -100,19 +125,14 @@ public class SPdfApplication {
|
||||
|
||||
app.run(args);
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Thread interrupted while sleeping", e);
|
||||
}
|
||||
|
||||
// Ensure directories are created
|
||||
try {
|
||||
Files.createDirectories(Path.of("customFiles/static/"));
|
||||
Files.createDirectories(Path.of("customFiles/templates/"));
|
||||
} catch (Exception e) {
|
||||
logger.error("Error creating directories: {}", e.getMessage());
|
||||
}
|
||||
|
||||
printStartupLogs();
|
||||
}
|
||||
|
||||
|
||||
@@ -160,4 +160,27 @@ public class AppConfig {
|
||||
public String accessibilityStatement() {
|
||||
return applicationProperties.getLegal().getAccessibilityStatement();
|
||||
}
|
||||
|
||||
@Bean(name = "analyticsPrompt")
|
||||
public boolean analyticsPrompt() {
|
||||
return applicationProperties.getSystem().getEnableAnalytics() == null
|
||||
|| "undefined".equals(applicationProperties.getSystem().getEnableAnalytics());
|
||||
}
|
||||
|
||||
@Bean(name = "analyticsEnabled")
|
||||
public boolean analyticsEnabled() {
|
||||
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
||||
return applicationProperties.getSystem().getEnableAnalytics() != null
|
||||
&& Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics());
|
||||
}
|
||||
|
||||
@Bean(name = "StirlingPDFLabel")
|
||||
public String stirlingPDFLabel() {
|
||||
return "Stirling-PDF" + " v" + appVersion();
|
||||
}
|
||||
|
||||
@Bean(name = "UUID")
|
||||
public String uuid() {
|
||||
return applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Service
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||
public class InitialSetup {
|
||||
|
||||
@Autowired private ApplicationProperties applicationProperties;
|
||||
|
||||
@PostConstruct
|
||||
public void initUUIDKey() throws IOException {
|
||||
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||
if (!GeneralUtils.isValidUUID(uuid)) {
|
||||
uuid = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.UUID", uuid);
|
||||
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initSecretKey() throws IOException {
|
||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||
if (!GeneralUtils.isValidUUID(secretKey)) {
|
||||
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.key", secretKey);
|
||||
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Configuration
|
||||
public class Beans implements WebMvcConfigurer {
|
||||
public class LocaleConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@@ -13,6 +13,7 @@ import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
|
||||
@Component
|
||||
@@ -32,10 +33,11 @@ public class MetricsFilter extends OncePerRequestFilter {
|
||||
String uri = request.getRequestURI();
|
||||
|
||||
if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) {
|
||||
|
||||
HttpSession session = request.getSession(false);
|
||||
String sessionId = (session != null) ? session.getId() : "no-session";
|
||||
Counter counter =
|
||||
Counter.builder("http.requests")
|
||||
.tag("session", request.getSession().getId())
|
||||
.tag("session", sessionId)
|
||||
.tag("method", request.getMethod())
|
||||
.tag("uri", uri)
|
||||
.register(meterRegistry);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.posthog.java.PostHog;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
|
||||
@Configuration
|
||||
public class PostHogConfig {
|
||||
|
||||
@Value("${posthog.api.key}")
|
||||
private String posthogApiKey;
|
||||
|
||||
@Value("${posthog.host}")
|
||||
private String posthogHost;
|
||||
|
||||
private PostHog postHogClient;
|
||||
|
||||
@Bean
|
||||
public PostHog postHogClient() {
|
||||
postHogClient = new PostHog.Builder(posthogApiKey).host(posthogHost).build();
|
||||
return postHogClient;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdownPostHog() {
|
||||
if (postHogClient != null) {
|
||||
postHogClient.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// package stirling.software.SPDF.config.fingerprint;
|
||||
//
|
||||
// import java.io.IOException;
|
||||
//
|
||||
// import org.springframework.beans.factory.annotation.Autowired;
|
||||
// import org.springframework.stereotype.Component;
|
||||
// import org.springframework.web.filter.OncePerRequestFilter;
|
||||
//
|
||||
// import jakarta.servlet.FilterChain;
|
||||
// import jakarta.servlet.ServletException;
|
||||
// import jakarta.servlet.http.HttpServletRequest;
|
||||
// import jakarta.servlet.http.HttpServletResponse;
|
||||
// import jakarta.servlet.http.HttpSession;
|
||||
// import lombok.extern.slf4j.Slf4j;
|
||||
// import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
//
|
||||
//// @Component
|
||||
// @Slf4j
|
||||
// public class FingerprintBasedSessionFilter extends OncePerRequestFilter {
|
||||
// private final FingerprintGenerator fingerprintGenerator;
|
||||
// private final FingerprintBasedSessionManager sessionManager;
|
||||
//
|
||||
// @Autowired
|
||||
// public FingerprintBasedSessionFilter(
|
||||
// FingerprintGenerator fingerprintGenerator,
|
||||
// FingerprintBasedSessionManager sessionManager) {
|
||||
// this.fingerprintGenerator = fingerprintGenerator;
|
||||
// this.sessionManager = sessionManager;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void doFilterInternal(
|
||||
// HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
// throws ServletException, IOException {
|
||||
//
|
||||
// if (RequestUriUtils.isStaticResource(request.getContextPath(), request.getRequestURI())) {
|
||||
// filterChain.doFilter(request, response);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// String fingerprint = fingerprintGenerator.generateFingerprint(request);
|
||||
// log.debug("Generated fingerprint for request: {}", fingerprint);
|
||||
//
|
||||
// HttpSession session = request.getSession();
|
||||
// boolean isNewSession = session.isNew();
|
||||
// String sessionId = session.getId();
|
||||
//
|
||||
// if (isNewSession) {
|
||||
// log.info("New session created: {}", sessionId);
|
||||
// }
|
||||
//
|
||||
// if (!sessionManager.isFingerPrintAllowed(fingerprint)) {
|
||||
// log.info("Blocked fingerprint detected, redirecting: {}", fingerprint);
|
||||
// response.sendRedirect(request.getContextPath() + "/too-many-requests");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// session.setAttribute("userFingerprint", fingerprint);
|
||||
// session.setAttribute(
|
||||
// FingerprintBasedSessionManager.STARTUP_TIMESTAMP,
|
||||
// FingerprintBasedSessionManager.APP_STARTUP_TIME);
|
||||
//
|
||||
// sessionManager.registerFingerprint(fingerprint, sessionId);
|
||||
//
|
||||
// log.debug("Proceeding with request: {}", request.getRequestURI());
|
||||
// filterChain.doFilter(request, response);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,134 @@
|
||||
// package stirling.software.SPDF.config.fingerprint;
|
||||
//
|
||||
// import java.util.Iterator;
|
||||
// import java.util.Map;
|
||||
// import java.util.concurrent.ConcurrentHashMap;
|
||||
// import java.util.concurrent.TimeUnit;
|
||||
//
|
||||
// import org.springframework.scheduling.annotation.Scheduled;
|
||||
// import org.springframework.stereotype.Component;
|
||||
//
|
||||
// import jakarta.servlet.http.HttpSession;
|
||||
// import jakarta.servlet.http.HttpSessionAttributeListener;
|
||||
// import jakarta.servlet.http.HttpSessionEvent;
|
||||
// import jakarta.servlet.http.HttpSessionListener;
|
||||
// import lombok.AllArgsConstructor;
|
||||
// import lombok.Data;
|
||||
// import lombok.extern.slf4j.Slf4j;
|
||||
//
|
||||
// @Slf4j
|
||||
// @Component
|
||||
// public class FingerprintBasedSessionManager
|
||||
// implements HttpSessionListener, HttpSessionAttributeListener {
|
||||
// private static final ConcurrentHashMap<String, FingerprintInfo> activeFingerprints =
|
||||
// new ConcurrentHashMap<>();
|
||||
//
|
||||
// // To be reduced in later version to 8~
|
||||
// private static final int MAX_ACTIVE_FINGERPRINTS = 30;
|
||||
//
|
||||
// static final String STARTUP_TIMESTAMP = "appStartupTimestamp";
|
||||
// static final long APP_STARTUP_TIME = System.currentTimeMillis();
|
||||
// private static final long FINGERPRINT_EXPIRATION = TimeUnit.MINUTES.toMillis(30);
|
||||
//
|
||||
// @Override
|
||||
// public void sessionCreated(HttpSessionEvent se) {
|
||||
// HttpSession session = se.getSession();
|
||||
// String sessionId = session.getId();
|
||||
// String fingerprint = (String) session.getAttribute("userFingerprint");
|
||||
//
|
||||
// if (fingerprint == null) {
|
||||
// log.warn("Session created without fingerprint: {}", sessionId);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// synchronized (activeFingerprints) {
|
||||
// if (activeFingerprints.size() >= MAX_ACTIVE_FINGERPRINTS
|
||||
// && !activeFingerprints.containsKey(fingerprint)) {
|
||||
// log.info("Max fingerprints reached. Marking session as blocked: {}", sessionId);
|
||||
// session.setAttribute("blocked", true);
|
||||
// } else {
|
||||
// activeFingerprints.put(
|
||||
// fingerprint, new FingerprintInfo(sessionId, System.currentTimeMillis()));
|
||||
// log.info(
|
||||
// "New fingerprint registered: {}. Total active fingerprints: {}",
|
||||
// fingerprint,
|
||||
// activeFingerprints.size());
|
||||
// }
|
||||
// session.setAttribute(STARTUP_TIMESTAMP, APP_STARTUP_TIME);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void sessionDestroyed(HttpSessionEvent se) {
|
||||
// HttpSession session = se.getSession();
|
||||
// String fingerprint = (String) session.getAttribute("userFingerprint");
|
||||
//
|
||||
// if (fingerprint != null) {
|
||||
// synchronized (activeFingerprints) {
|
||||
// activeFingerprints.remove(fingerprint);
|
||||
// log.info(
|
||||
// "Fingerprint removed: {}. Total active fingerprints: {}",
|
||||
// fingerprint,
|
||||
// activeFingerprints.size());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public boolean isFingerPrintAllowed(String fingerprint) {
|
||||
// synchronized (activeFingerprints) {
|
||||
// return activeFingerprints.size() < MAX_ACTIVE_FINGERPRINTS
|
||||
// || activeFingerprints.containsKey(fingerprint);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void registerFingerprint(String fingerprint, String sessionId) {
|
||||
// synchronized (activeFingerprints) {
|
||||
// activeFingerprints.put(
|
||||
// fingerprint, new FingerprintInfo(sessionId, System.currentTimeMillis()));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void unregisterFingerprint(String fingerprint) {
|
||||
// synchronized (activeFingerprints) {
|
||||
// activeFingerprints.remove(fingerprint);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Scheduled(fixedRate = 1800000) // Run every 30 mins
|
||||
// public void cleanupStaleFingerprints() {
|
||||
// log.info("Starting cleanup of stale fingerprints");
|
||||
// long now = System.currentTimeMillis();
|
||||
// int removedCount = 0;
|
||||
//
|
||||
// synchronized (activeFingerprints) {
|
||||
// Iterator<Map.Entry<String, FingerprintInfo>> iterator =
|
||||
// activeFingerprints.entrySet().iterator();
|
||||
// while (iterator.hasNext()) {
|
||||
// Map.Entry<String, FingerprintInfo> entry = iterator.next();
|
||||
// FingerprintInfo info = entry.getValue();
|
||||
//
|
||||
// if (now - info.getLastAccessTime() > FINGERPRINT_EXPIRATION) {
|
||||
// iterator.remove();
|
||||
// removedCount++;
|
||||
// log.info("Removed stale fingerprint: {}", entry.getKey());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// log.info("Cleanup complete. Removed {} stale fingerprints", removedCount);
|
||||
// }
|
||||
//
|
||||
// public void updateLastAccessTime(String fingerprint) {
|
||||
// FingerprintInfo info = activeFingerprints.get(fingerprint);
|
||||
// if (info != null) {
|
||||
// info.setLastAccessTime(System.currentTimeMillis());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Data
|
||||
// @AllArgsConstructor
|
||||
// private static class FingerprintInfo {
|
||||
// private String sessionId;
|
||||
// private long lastAccessTime;
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,77 @@
|
||||
// package stirling.software.SPDF.config.fingerprint;
|
||||
//
|
||||
// import java.security.MessageDigest;
|
||||
// import java.security.NoSuchAlgorithmException;
|
||||
//
|
||||
// import org.springframework.stereotype.Component;
|
||||
//
|
||||
// import jakarta.servlet.http.HttpServletRequest;
|
||||
//
|
||||
// @Component
|
||||
// public class FingerprintGenerator {
|
||||
//
|
||||
// public String generateFingerprint(HttpServletRequest request) {
|
||||
// if (request == null) {
|
||||
// return "";
|
||||
// }
|
||||
// StringBuilder fingerprintBuilder = new StringBuilder();
|
||||
//
|
||||
// // Add IP address
|
||||
// fingerprintBuilder.append(request.getRemoteAddr());
|
||||
//
|
||||
// // Add X-Forwarded-For header if present (for clients behind proxies)
|
||||
// String forwardedFor = request.getHeader("X-Forwarded-For");
|
||||
// if (forwardedFor != null) {
|
||||
// fingerprintBuilder.append(forwardedFor);
|
||||
// }
|
||||
//
|
||||
// // Add User-Agent
|
||||
// String userAgent = request.getHeader("User-Agent");
|
||||
// if (userAgent != null) {
|
||||
// fingerprintBuilder.append(userAgent);
|
||||
// }
|
||||
//
|
||||
// // Add Accept-Language header
|
||||
// String acceptLanguage = request.getHeader("Accept-Language");
|
||||
// if (acceptLanguage != null) {
|
||||
// fingerprintBuilder.append(acceptLanguage);
|
||||
// }
|
||||
//
|
||||
// // Add Accept header
|
||||
// String accept = request.getHeader("Accept");
|
||||
// if (accept != null) {
|
||||
// fingerprintBuilder.append(accept);
|
||||
// }
|
||||
//
|
||||
// // Add Connection header
|
||||
// String connection = request.getHeader("Connection");
|
||||
// if (connection != null) {
|
||||
// fingerprintBuilder.append(connection);
|
||||
// }
|
||||
//
|
||||
// // Add server port
|
||||
// fingerprintBuilder.append(request.getServerPort());
|
||||
//
|
||||
// // Add secure flag
|
||||
// fingerprintBuilder.append(request.isSecure());
|
||||
//
|
||||
// // Generate a hash of the fingerprint
|
||||
// return generateHash(fingerprintBuilder.toString());
|
||||
// }
|
||||
//
|
||||
// private String generateHash(String input) {
|
||||
// try {
|
||||
// MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
// byte[] hash = digest.digest(input.getBytes());
|
||||
// StringBuilder hexString = new StringBuilder();
|
||||
// for (byte b : hash) {
|
||||
// String hex = Integer.toHexString(0xff & b);
|
||||
// if (hex.length() == 1) hexString.append('0');
|
||||
// hexString.append(hex);
|
||||
// }
|
||||
// return hexString.toString();
|
||||
// } catch (NoSuchAlgorithmException e) {
|
||||
// throw new RuntimeException("Failed to generate fingerprint hash", e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -1,4 +1,4 @@
|
||||
package stirling.software.SPDF.config;
|
||||
package stirling.software.SPDF.config.interfaces;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -1,4 +1,4 @@
|
||||
package stirling.software.SPDF.config;
|
||||
package stirling.software.SPDF.config.interfaces;
|
||||
|
||||
public interface ShowAdminInterface {
|
||||
default boolean getShowUpdateOnlyAdmins() {
|
||||
@@ -7,7 +7,7 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.config.ShowAdminInterface;
|
||||
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.repository.UserRepository;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -14,9 +16,12 @@ import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FirstLoginFilter extends OncePerRequestFilter {
|
||||
|
||||
@@ -50,6 +55,22 @@ public class FirstLoginFilter extends OncePerRequestFilter {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
HttpSession session = request.getSession(true);
|
||||
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
|
||||
String creationTime = timeFormat.format(new Date(session.getCreationTime()));
|
||||
|
||||
log.debug(
|
||||
"Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}",
|
||||
session.isNew(),
|
||||
creationTime,
|
||||
session.getId(),
|
||||
request.getRemoteAddr(),
|
||||
request.getHeader("User-Agent"),
|
||||
request.getHeader("Referer"),
|
||||
request.getRequestURL().toString());
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.simpleyaml.configuration.file.YamlFile;
|
||||
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
|
||||
@@ -39,15 +34,6 @@ public class InitialSecuritySetup {
|
||||
initializeInternalApiUser();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initSecretKey() throws IOException {
|
||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||
if (!isValidUUID(secretKey)) {
|
||||
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||
saveKeyToConfig(secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeAdminUser() throws IOException {
|
||||
String initialUsername =
|
||||
applicationProperties.getSecurity().getInitialLogin().getUsername();
|
||||
@@ -89,33 +75,4 @@ public class InitialSecuritySetup {
|
||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveKeyToConfig(String key) throws IOException {
|
||||
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||
|
||||
final YamlFile settingsYml = new YamlFile(path.toFile());
|
||||
DumperOptions yamlOptionssettingsYml =
|
||||
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
|
||||
yamlOptionssettingsYml.setSplitLines(false);
|
||||
|
||||
settingsYml.loadWithComments();
|
||||
|
||||
settingsYml
|
||||
.path("AutomaticallyGenerated.key")
|
||||
.set(key)
|
||||
.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||
settingsYml.save();
|
||||
}
|
||||
|
||||
private boolean isValidUUID(String uuid) {
|
||||
if (uuid == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
UUID.fromString(uuid);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,55 @@
|
||||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||
import stirling.software.SPDF.config.security.saml.ConvertResponseToAuthentication;
|
||||
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler;
|
||||
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler;
|
||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||
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.provider.GithubProvider;
|
||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@Slf4j
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@Autowired private CustomUserDetailsService userDetailsService;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||
@Autowired(required = false)
|
||||
private GrantedAuthoritiesMapper userAuthoritiesMapper;
|
||||
|
||||
@Autowired(required = false)
|
||||
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
@@ -73,13 +71,18 @@ public class SecurityConfiguration {
|
||||
@Autowired private FirstLoginFilter firstLoginFilter;
|
||||
@Autowired private SessionPersistentRegistry sessionRegistry;
|
||||
|
||||
@Autowired private ConvertResponseToAuthentication convertResponseToAuthentication;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
http.authenticationManager(authenticationManager(http));
|
||||
|
||||
if (loginEnabledValue) {
|
||||
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
http.addFilterBefore(
|
||||
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
}
|
||||
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
http.sessionManagement(
|
||||
@@ -135,6 +138,7 @@ public class SecurityConfiguration {
|
||||
|
||||
return trimmedUri.startsWith("/login")
|
||||
|| trimmedUri.startsWith("/oauth")
|
||||
|| trimmedUri.startsWith("/saml2")
|
||||
|| trimmedUri.endsWith(".svg")
|
||||
|| trimmedUri.startsWith(
|
||||
"/register")
|
||||
@@ -184,191 +188,82 @@ public class SecurityConfiguration {
|
||||
userService,
|
||||
loginAttemptService))
|
||||
.userAuthoritiesMapper(
|
||||
userAuthoritiesMapper())))
|
||||
userAuthoritiesMapper)))
|
||||
.logout(
|
||||
logout ->
|
||||
logout.logoutSuccessHandler(
|
||||
new CustomOAuth2LogoutSuccessHandler(
|
||||
applicationProperties)));
|
||||
}
|
||||
|
||||
// Handle SAML
|
||||
if (applicationProperties.getSecurity().getSaml() != null
|
||||
&& applicationProperties.getSecurity().getSaml().getEnabled()
|
||||
&& !applicationProperties
|
||||
.getSecurity()
|
||||
.getLoginMethod()
|
||||
.equalsIgnoreCase("normal")) {
|
||||
http.saml2Login(
|
||||
saml2 -> {
|
||||
saml2.loginPage("/saml2")
|
||||
.relyingPartyRegistrationRepository(
|
||||
relyingPartyRegistrationRepository)
|
||||
.successHandler(
|
||||
new CustomSAMLAuthenticationSuccessHandler(
|
||||
loginAttemptService,
|
||||
userService,
|
||||
applicationProperties))
|
||||
.failureHandler(
|
||||
new CustomSAMLAuthenticationFailureHandler());
|
||||
})
|
||||
.addFilterBefore(
|
||||
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
|
||||
}
|
||||
} else {
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||
http.csrf(csrf -> csrf.disable());
|
||||
}
|
||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||
}
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// Client Registration Repository for OAUTH2 OIDC Login
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "security.oauth2.enabled",
|
||||
name = "security.saml.enabled",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||
List<ClientRegistration> registrations = new ArrayList<>();
|
||||
|
||||
githubClientRegistration().ifPresent(registrations::add);
|
||||
oidcClientRegistration().ifPresent(registrations::add);
|
||||
googleClientRegistration().ifPresent(registrations::add);
|
||||
keycloakClientRegistration().ifPresent(registrations::add);
|
||||
|
||||
if (registrations.isEmpty()) {
|
||||
logger.error("At least one OAuth2 provider must be configured");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
return new InMemoryClientRegistrationRepository(registrations);
|
||||
public AuthenticationProvider samlAuthenticationProvider() {
|
||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||
new OpenSaml4AuthenticationProvider();
|
||||
authenticationProvider.setResponseAuthenticationConverter(convertResponseToAuthentication);
|
||||
return authenticationProvider;
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> googleClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
GoogleProvider google = client.getGoogle();
|
||||
return google != null && google.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistration.withRegistrationId(google.getName())
|
||||
.clientId(google.getClientId())
|
||||
.clientSecret(google.getClientSecret())
|
||||
.scope(google.getScopes())
|
||||
.authorizationUri(google.getAuthorizationuri())
|
||||
.tokenUri(google.getTokenuri())
|
||||
.userInfoUri(google.getUserinfouri())
|
||||
.userNameAttributeName(google.getUseAsUsername())
|
||||
.clientName(google.getClientName())
|
||||
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
||||
.authorizationGrantType(
|
||||
org.springframework.security.oauth2.core
|
||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
// @Bean
|
||||
// public AuthenticationProvider daoAuthenticationProvider() {
|
||||
// DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
// provider.setUserDetailsService(userDetailsService); // UserDetailsService
|
||||
// provider.setPasswordEncoder(passwordEncoder()); // PasswordEncoder
|
||||
// return provider;
|
||||
// }
|
||||
|
||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
KeycloakProvider keycloak = client.getKeycloak();
|
||||
|
||||
return keycloak != null && keycloak.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||
.registrationId(keycloak.getName())
|
||||
.clientId(keycloak.getClientId())
|
||||
.clientSecret(keycloak.getClientSecret())
|
||||
.scope(keycloak.getScopes())
|
||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
||||
.clientName(keycloak.getClientName())
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> githubClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||
if (oauth == null || !oauth.getEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Client client = oauth.getClient();
|
||||
if (client == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
GithubProvider github = client.getGithub();
|
||||
return github != null && github.isSettingsValid()
|
||||
? Optional.of(
|
||||
ClientRegistration.withRegistrationId(github.getName())
|
||||
.clientId(github.getClientId())
|
||||
.clientSecret(github.getClientSecret())
|
||||
.scope(github.getScopes())
|
||||
.authorizationUri(github.getAuthorizationuri())
|
||||
.tokenUri(github.getTokenuri())
|
||||
.userInfoUri(github.getUserinfouri())
|
||||
.userNameAttributeName(github.getUseAsUsername())
|
||||
.clientName(github.getClientName())
|
||||
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
||||
.authorizationGrantType(
|
||||
org.springframework.security.oauth2.core
|
||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.build())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||
if (oauth == null
|
||||
|| oauth.getIssuer() == null
|
||||
|| oauth.getIssuer().isEmpty()
|
||||
|| oauth.getClientId() == null
|
||||
|| oauth.getClientId().isEmpty()
|
||||
|| oauth.getClientSecret() == null
|
||||
|| oauth.getClientSecret().isEmpty()
|
||||
|| oauth.getScopes() == null
|
||||
|| oauth.getScopes().isEmpty()
|
||||
|| oauth.getUseAsUsername() == null
|
||||
|| oauth.getUseAsUsername().isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(
|
||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||
.registrationId("oidc")
|
||||
.clientId(oauth.getClientId())
|
||||
.clientSecret(oauth.getClientSecret())
|
||||
.scope(oauth.getScopes())
|
||||
.userNameAttributeName(oauth.getUseAsUsername())
|
||||
.clientName("OIDC")
|
||||
.build());
|
||||
}
|
||||
|
||||
/*
|
||||
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
||||
This is required for the internal; 'hasRole()' function to give out the correct role.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "security.oauth2.enabled",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||
return (authorities) -> {
|
||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
||||
AuthenticationManagerBuilder authenticationManagerBuilder =
|
||||
http.getSharedObject(AuthenticationManagerBuilder.class);
|
||||
|
||||
authorities.forEach(
|
||||
authority -> {
|
||||
// Add existing OAUTH2 Authorities
|
||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
||||
// authenticationManagerBuilder =
|
||||
// authenticationManagerBuilder.authenticationProvider(
|
||||
// daoAuthenticationProvider()); // Benutzername/Passwort
|
||||
|
||||
// Add Authorities from database for existing user, if user is present.
|
||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
||||
String useAsUsername =
|
||||
applicationProperties
|
||||
.getSecurity()
|
||||
.getOauth2()
|
||||
.getUseAsUsername();
|
||||
Optional<User> userOpt =
|
||||
userService.findByUsernameIgnoreCase(
|
||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
||||
if (userOpt.isPresent()) {
|
||||
User user = userOpt.get();
|
||||
if (user != null) {
|
||||
mappedAuthorities.add(
|
||||
new SimpleGrantedAuthority(
|
||||
userService.findRole(user).getAuthority()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return mappedAuthorities;
|
||||
};
|
||||
if (applicationProperties.getSecurity().getSaml() != null
|
||||
&& applicationProperties.getSecurity().getSaml().getEnabled()) {
|
||||
authenticationManagerBuilder.authenticationProvider(
|
||||
samlAuthenticationProvider()); // SAML
|
||||
}
|
||||
return authenticationManagerBuilder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -386,4 +281,14 @@ public class SecurityConfiguration {
|
||||
public boolean activSecurity() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// // Only Dev test
|
||||
// @Bean
|
||||
// public WebSecurityCustomizer webSecurityCustomizer() {
|
||||
// return (web) ->
|
||||
// web.ignoring()
|
||||
// .requestMatchers(
|
||||
// "/css/**", "/images/**", "/js/**", "/**.svg",
|
||||
// "/pdfjs-legacy/**");
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -30,13 +29,18 @@ import stirling.software.SPDF.model.User;
|
||||
@Component
|
||||
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired @Lazy private UserService userService;
|
||||
private final UserService userService;
|
||||
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||
private final boolean loginEnabledValue;
|
||||
|
||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("loginEnabled")
|
||||
public boolean loginEnabledValue;
|
||||
public UserAuthenticationFilter(
|
||||
@Lazy UserService userService,
|
||||
SessionPersistentRegistry sessionPersistentRegistry,
|
||||
@Qualifier("loginEnabled") boolean loginEnabledValue) {
|
||||
this.userService = userService;
|
||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||
this.loginEnabledValue = loginEnabledValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@@ -51,6 +55,19 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
String requestURI = request.getRequestURI();
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
// Check for session expiration (unsure if needed)
|
||||
// if (authentication != null && authentication.isAuthenticated()) {
|
||||
// String sessionId = request.getSession().getId();
|
||||
// SessionInformation sessionInfo =
|
||||
// sessionPersistentRegistry.getSessionInformation(sessionId);
|
||||
//
|
||||
// if (sessionInfo != null && sessionInfo.isExpired()) {
|
||||
// SecurityContextHolder.clearContext();
|
||||
// response.sendRedirect(request.getContextPath() + "/login?expired=true");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Check for API key in the request headers if no authentication exists
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
String apiKey = request.getHeader("X-API-Key");
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||
import stirling.software.SPDF.model.AuthenticationType;
|
||||
@@ -44,6 +44,10 @@ public class UserService implements UserServiceInterface {
|
||||
|
||||
@Autowired DatabaseBackupInterface databaseBackupHelper;
|
||||
|
||||
public long getTotalUserCount() {
|
||||
return userRepository.count();
|
||||
}
|
||||
|
||||
// Handle OAUTH2 login and user auto creation.
|
||||
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
||||
throws IllegalArgumentException, IOException {
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||
import stirling.software.SPDF.utils.FileInfo;
|
||||
|
||||
@Slf4j
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.opensaml.saml.saml2.core.Assertion;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ConvertResponseToAuthentication
|
||||
implements Converter<ResponseToken, Saml2Authentication> {
|
||||
|
||||
private final Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup;
|
||||
|
||||
public ConvertResponseToAuthentication(
|
||||
Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup) {
|
||||
this.saml2AuthorityAttributeLookup = saml2AuthorityAttributeLookup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Saml2Authentication convert(ResponseToken responseToken) {
|
||||
final Assertion assertion =
|
||||
CollectionUtils.firstElement(responseToken.getResponse().getAssertions());
|
||||
final Map<String, List<Object>> attributes =
|
||||
SamlAssertionUtils.getAssertionAttributes(assertion);
|
||||
final String registrationId =
|
||||
responseToken.getToken().getRelyingPartyRegistration().getRegistrationId();
|
||||
final ScimSaml2AuthenticatedPrincipal principal =
|
||||
new ScimSaml2AuthenticatedPrincipal(
|
||||
assertion,
|
||||
attributes,
|
||||
saml2AuthorityAttributeLookup.getIdentityMappings(registrationId));
|
||||
final Collection<? extends GrantedAuthority> assertionAuthorities =
|
||||
getAssertionAuthorities(
|
||||
attributes,
|
||||
saml2AuthorityAttributeLookup.getAuthorityAttribute(registrationId));
|
||||
return new Saml2Authentication(
|
||||
principal, responseToken.getToken().getSaml2Response(), assertionAuthorities);
|
||||
}
|
||||
|
||||
private static Collection<? extends GrantedAuthority> getAssertionAuthorities(
|
||||
final Map<String, List<Object>> attributes, final String authoritiesAttributeName) {
|
||||
if (attributes == null || attributes.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
final List<Object> groups = new ArrayList<>(attributes.get(authoritiesAttributeName));
|
||||
return groups.stream()
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.map(String::toLowerCase)
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class CustomSAMLAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException exception)
|
||||
throws IOException, ServletException {
|
||||
|
||||
if (exception instanceof BadCredentialsException) {
|
||||
log.error("BadCredentialsException", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
||||
return;
|
||||
}
|
||||
if (exception instanceof DisabledException) {
|
||||
log.error("User is deactivated: ", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
|
||||
return;
|
||||
}
|
||||
if (exception instanceof LockedException) {
|
||||
log.error("Account locked: ", exception);
|
||||
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||
return;
|
||||
}
|
||||
if (exception instanceof Saml2AuthenticationException) {
|
||||
log.error("SAML2 Authentication error: ", exception);
|
||||
getRedirectStrategy()
|
||||
.sendRedirect(request, response, "/logout?error=saml2AuthenticationError");
|
||||
return;
|
||||
}
|
||||
log.error("Unhandled authentication exception", exception);
|
||||
super.onAuthenticationFailure(request, response, exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.security.authentication.LockedException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.security.LoginAttemptService;
|
||||
import stirling.software.SPDF.config.security.UserService;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||
import stirling.software.SPDF.model.AuthenticationType;
|
||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||
|
||||
@Slf4j
|
||||
public class CustomSAMLAuthenticationSuccessHandler
|
||||
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||
|
||||
private LoginAttemptService loginAttemptService;
|
||||
private UserService userService;
|
||||
private ApplicationProperties applicationProperties;
|
||||
|
||||
public CustomSAMLAuthenticationSuccessHandler(
|
||||
LoginAttemptService loginAttemptService,
|
||||
UserService userService,
|
||||
ApplicationProperties applicationProperties) {
|
||||
this.loginAttemptService = loginAttemptService;
|
||||
this.userService = userService;
|
||||
this.applicationProperties = applicationProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(
|
||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws ServletException, IOException {
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
String username = "";
|
||||
|
||||
if (principal instanceof OAuth2User) {
|
||||
OAuth2User oauthUser = (OAuth2User) principal;
|
||||
username = oauthUser.getName();
|
||||
} else if (principal instanceof UserDetails) {
|
||||
UserDetails oauthUser = (UserDetails) principal;
|
||||
username = oauthUser.getUsername();
|
||||
} else if (principal instanceof ScimSaml2AuthenticatedPrincipal) {
|
||||
ScimSaml2AuthenticatedPrincipal samlPrincipal =
|
||||
(ScimSaml2AuthenticatedPrincipal) principal;
|
||||
username = samlPrincipal.getName();
|
||||
}
|
||||
|
||||
// Get the saved request
|
||||
HttpSession session = request.getSession(false);
|
||||
String contextPath = request.getContextPath();
|
||||
SavedRequest savedRequest =
|
||||
(session != null)
|
||||
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
||||
: null;
|
||||
|
||||
if (savedRequest != null
|
||||
&& !RequestUriUtils.isStaticResource(contextPath, savedRequest.getRedirectUrl())) {
|
||||
// Redirect to the original destination
|
||||
super.onAuthenticationSuccess(request, response, authentication);
|
||||
} else {
|
||||
OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();
|
||||
|
||||
if (loginAttemptService.isBlocked(username)) {
|
||||
if (session != null) {
|
||||
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
|
||||
}
|
||||
throw new LockedException(
|
||||
"Your account has been locked due to too many failed login attempts.");
|
||||
}
|
||||
if (userService.usernameExistsIgnoreCase(username)
|
||||
&& userService.hasPassword(username)
|
||||
&& !userService.isAuthenticationTypeByUsername(
|
||||
username, AuthenticationType.OAUTH2)
|
||||
&& oAuth.getAutoCreateUser()) {
|
||||
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (oAuth.getBlockRegistration()
|
||||
&& !userService.usernameExistsIgnoreCase(username)) {
|
||||
response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true");
|
||||
return;
|
||||
}
|
||||
if (principal instanceof OAuth2User) {
|
||||
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
|
||||
}
|
||||
response.sendRedirect(contextPath + "/");
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SAMLLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(
|
||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
|
||||
String redirectUrl = determineTargetUrl(request, response, authentication);
|
||||
|
||||
if (response.isCommitted()) {
|
||||
log.debug("Response has already been committed. Unable to redirect to " + redirectUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
||||
}
|
||||
|
||||
protected String determineTargetUrl(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Authentication authentication) {
|
||||
// Default to the root URL
|
||||
return "/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
public interface Saml2AuthorityAttributeLookup {
|
||||
String getAuthorityAttribute(String registrationId);
|
||||
|
||||
SimpleScimMappings getIdentityMappings(String registrationId);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class Saml2AuthorityAttributeLookupImpl implements Saml2AuthorityAttributeLookup {
|
||||
|
||||
@Override
|
||||
public String getAuthorityAttribute(String registrationId) {
|
||||
return "authorityAttributeName";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleScimMappings getIdentityMappings(String registrationId) {
|
||||
return new SimpleScimMappings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
import org.opensaml.core.xml.XMLObject;
|
||||
import org.opensaml.core.xml.schema.*;
|
||||
import org.opensaml.saml.saml2.core.Assertion;
|
||||
|
||||
public class SamlAssertionUtils {
|
||||
|
||||
public static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) {
|
||||
Map<String, List<Object>> attributeMap = new LinkedHashMap<>();
|
||||
|
||||
assertion
|
||||
.getAttributeStatements()
|
||||
.forEach(
|
||||
attributeStatement -> {
|
||||
attributeStatement
|
||||
.getAttributes()
|
||||
.forEach(
|
||||
attribute -> {
|
||||
List<Object> attributeValues = new ArrayList<>();
|
||||
|
||||
attribute
|
||||
.getAttributeValues()
|
||||
.forEach(
|
||||
xmlObject -> {
|
||||
Object attributeValue =
|
||||
getXmlObjectValue(
|
||||
xmlObject);
|
||||
if (attributeValue != null) {
|
||||
attributeValues.add(
|
||||
attributeValue);
|
||||
}
|
||||
});
|
||||
|
||||
attributeMap.put(
|
||||
attribute.getName(), attributeValues);
|
||||
});
|
||||
});
|
||||
|
||||
return attributeMap;
|
||||
}
|
||||
|
||||
public static Object getXmlObjectValue(XMLObject xmlObject) {
|
||||
if (xmlObject instanceof XSAny) {
|
||||
return ((XSAny) xmlObject).getTextContent();
|
||||
} else if (xmlObject instanceof XSString) {
|
||||
return ((XSString) xmlObject).getValue();
|
||||
} else if (xmlObject instanceof XSInteger) {
|
||||
return ((XSInteger) xmlObject).getValue();
|
||||
} else if (xmlObject instanceof XSURI) {
|
||||
return ((XSURI) xmlObject).getURI();
|
||||
} else if (xmlObject instanceof XSBoolean) {
|
||||
return ((XSBoolean) xmlObject).getValue().getValue();
|
||||
} else if (xmlObject instanceof XSDateTime) {
|
||||
Instant dateTime = ((XSDateTime) xmlObject).getValue();
|
||||
return (dateTime != null) ? Instant.ofEpochMilli(dateTime.toEpochMilli()) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class SamlConfig {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "security.saml.enabled",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository()
|
||||
throws CertificateException {
|
||||
RelyingPartyRegistration registration =
|
||||
RelyingPartyRegistrations.fromMetadataLocation(
|
||||
applicationProperties
|
||||
.getSecurity()
|
||||
.getSaml()
|
||||
.getIdpMetadataLocation())
|
||||
.entityId(applicationProperties.getSecurity().getSaml().getEntityId())
|
||||
.registrationId(
|
||||
applicationProperties.getSecurity().getSaml().getRegistrationId())
|
||||
.build();
|
||||
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.opensaml.saml.saml2.core.Assertion;
|
||||
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.unboundid.scim2.common.types.Email;
|
||||
import com.unboundid.scim2.common.types.Name;
|
||||
import com.unboundid.scim2.common.types.UserResource;
|
||||
|
||||
public class ScimSaml2AuthenticatedPrincipal implements AuthenticatedPrincipal, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final transient UserResource userResource;
|
||||
|
||||
public ScimSaml2AuthenticatedPrincipal(
|
||||
final Assertion assertion,
|
||||
final Map<String, List<Object>> attributes,
|
||||
final SimpleScimMappings attributeMappings) {
|
||||
Assert.notNull(assertion, "assertion cannot be null");
|
||||
Assert.notNull(assertion.getSubject(), "assertion subject cannot be null");
|
||||
Assert.notNull(
|
||||
assertion.getSubject().getNameID(), "assertion subject NameID cannot be null");
|
||||
Assert.notNull(attributes, "attributes cannot be null");
|
||||
Assert.notNull(attributeMappings, "attributeMappings cannot be null");
|
||||
|
||||
final Name name =
|
||||
new Name()
|
||||
.setFamilyName(
|
||||
getAttribute(
|
||||
attributes,
|
||||
attributeMappings,
|
||||
SimpleScimMappings::getFamilyName))
|
||||
.setGivenName(
|
||||
getAttribute(
|
||||
attributes,
|
||||
attributeMappings,
|
||||
SimpleScimMappings::getGivenName));
|
||||
|
||||
final List<Email> emails = new ArrayList<>(1);
|
||||
emails.add(
|
||||
new Email()
|
||||
.setValue(
|
||||
getAttribute(
|
||||
attributes,
|
||||
attributeMappings,
|
||||
SimpleScimMappings::getEmail))
|
||||
.setPrimary(true));
|
||||
|
||||
userResource =
|
||||
new UserResource()
|
||||
.setUserName(assertion.getSubject().getNameID().getValue())
|
||||
.setName(name)
|
||||
.setEmails(emails);
|
||||
}
|
||||
|
||||
private static String getAttribute(
|
||||
final Map<String, List<Object>> attributes,
|
||||
final SimpleScimMappings simpleScimMappings,
|
||||
final Function<SimpleScimMappings, String> attributeMapper) {
|
||||
|
||||
final String key = attributeMapper.apply(simpleScimMappings);
|
||||
|
||||
final List<Object> values = attributes.getOrDefault(key, Collections.emptyList());
|
||||
|
||||
return values.stream()
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.userResource.getUserName();
|
||||
}
|
||||
|
||||
public UserResource getUserResource() {
|
||||
return this.userResource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package stirling.software.SPDF.config.security.saml;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SimpleScimMappings {
|
||||
String givenName;
|
||||
String familyName;
|
||||
String email;
|
||||
}
|
||||
@@ -11,16 +11,19 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public class CustomHttpSessionListener implements HttpSessionListener {
|
||||
|
||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
|
||||
@Autowired
|
||||
public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) {
|
||||
super();
|
||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionCreated(HttpSessionEvent se) {
|
||||
log.info("Session created: " + se.getSession().getId());
|
||||
}
|
||||
public void sessionCreated(HttpSessionEvent se) {}
|
||||
|
||||
@Override
|
||||
public void sessionDestroyed(HttpSessionEvent se) {
|
||||
log.info("Session destroyed: " + se.getSession().getId());
|
||||
sessionPersistentRegistry.expireSession(se.getSession().getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,14 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
||||
}
|
||||
|
||||
if (principalName != null) {
|
||||
// Clear old sessions for the principal (unsure if needed)
|
||||
// List<SessionEntity> existingSessions =
|
||||
// sessionRepository.findByPrincipalName(principalName);
|
||||
// for (SessionEntity session : existingSessions) {
|
||||
// session.setExpired(true);
|
||||
// sessionRepository.save(session);
|
||||
// }
|
||||
|
||||
SessionEntity sessionEntity = new SessionEntity();
|
||||
sessionEntity.setSessionId(sessionId);
|
||||
sessionEntity.setPrincipalName(principalName);
|
||||
|
||||
@@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.general.CropPdfForm;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.service.PostHogService;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -36,9 +37,13 @@ public class CropController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
private final PostHogService postHogService;
|
||||
|
||||
@Autowired
|
||||
public CropController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
public CropController(
|
||||
CustomPDDocumentFactory pdfDocumentFactory, PostHogService postHogService) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.postHogService = postHogService;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
@Controller
|
||||
@Tag(name = "Settings", description = "Settings APIs")
|
||||
@RequestMapping("/api/v1/settings")
|
||||
@Hidden
|
||||
public class SettingsController {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@PostMapping("/update-enable-analytics")
|
||||
@Hidden
|
||||
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
|
||||
if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
|
||||
.body(
|
||||
"Setting has already been set, To adjust please edit /config/settings.yml");
|
||||
}
|
||||
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
||||
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
||||
return ResponseEntity.ok("Updated");
|
||||
}
|
||||
}
|
||||
@@ -60,8 +60,6 @@ public class SplitPDFController {
|
||||
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
||||
int totalPages = document.getNumberOfPages();
|
||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||
System.out.println(
|
||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
if (!pageNumbers.contains(totalPages - 1)) {
|
||||
// Create a mutable ArrayList so we can add to it
|
||||
pageNumbers = new ArrayList<>(pageNumbers);
|
||||
|
||||
@@ -32,9 +32,9 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import stirling.software.SPDF.config.PdfMetadataService;
|
||||
import stirling.software.SPDF.model.PdfMetadata;
|
||||
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
|
||||
import stirling.software.SPDF.service.PdfMetadataService;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
@@ -67,15 +67,6 @@ public class SplitPdfByChaptersController {
|
||||
}
|
||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||
|
||||
// checks if the document is encrypted by an empty user password
|
||||
if (sourceDocument.isEncrypted()) {
|
||||
try {
|
||||
sourceDocument.setAllSecurityToBeRemoved(true);
|
||||
logger.info("Removing security from the source document ");
|
||||
} catch (Exception e) {
|
||||
logger.warn("Cannot decrypt the pdf");
|
||||
}
|
||||
}
|
||||
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
||||
|
||||
if (outline == null) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.config.security.UserService;
|
||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.SPDF.model.AuthenticationType;
|
||||
@@ -40,6 +41,7 @@ import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
||||
@Controller
|
||||
@Tag(name = "User", description = "User APIs")
|
||||
@RequestMapping("/api/v1/user")
|
||||
@Slf4j
|
||||
public class UserController {
|
||||
|
||||
@Autowired private UserService userService;
|
||||
@@ -191,13 +193,11 @@ public class UserController {
|
||||
Map<String, String[]> paramMap = request.getParameterMap();
|
||||
Map<String, String> updates = new HashMap<>();
|
||||
|
||||
System.out.println("Received parameter map: " + paramMap);
|
||||
|
||||
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||
updates.put(entry.getKey(), entry.getValue()[0]);
|
||||
}
|
||||
|
||||
System.out.println("Processed updates: " + updates);
|
||||
log.debug("Processed updates: " + updates);
|
||||
|
||||
// Assuming you have a method in userService to update the settings for a user
|
||||
userService.updateUserSettings(principal.getName(), updates);
|
||||
@@ -209,7 +209,7 @@ public class UserController {
|
||||
@PostMapping("/admin/saveUser")
|
||||
public RedirectView saveUser(
|
||||
@RequestParam(name = "username", required = true) String username,
|
||||
@RequestParam(name = "password", required = true) String password,
|
||||
@RequestParam(name = "password", required = false) String password,
|
||||
@RequestParam(name = "role") String role,
|
||||
@RequestParam(name = "authType") String authType,
|
||||
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -14,7 +15,6 @@ import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.FileToPdf;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
// Disabled for now
|
||||
// @RestController
|
||||
// @Tag(name = "Convert", description = "Convert APIs")
|
||||
// @RequestMapping("/api/v1/convert")
|
||||
@@ -24,7 +24,7 @@ public class ConvertBookToPDFController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
// @Autowired
|
||||
@Autowired
|
||||
public ConvertBookToPDFController(
|
||||
CustomPDDocumentFactory pdfDocumentFactory,
|
||||
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||
@@ -66,6 +66,8 @@ public class ConvertBookToPDFController {
|
||||
}
|
||||
byte[] pdfBytes = FileToPdf.convertBookTypeToPdf(fileInput.getBytes(), originalFilename);
|
||||
|
||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||
|
||||
String outputFilename =
|
||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||
+ ".pdf"; // Remove file extension and append .pdf
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.FileToPdf;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
// Disabled for now
|
||||
// @RestController
|
||||
// @Tag(name = "Convert", description = "Convert APIs")
|
||||
// @RequestMapping("/api/v1/convert")
|
||||
@RestController
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@RequestMapping("/api/v1/convert")
|
||||
public class ConvertHtmlToPDF {
|
||||
|
||||
// @Autowired
|
||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||
private boolean bookAndHtmlFormatsInstalled;
|
||||
private final boolean bookAndHtmlFormatsInstalled;
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ConvertHtmlToPDF(
|
||||
CustomPDDocumentFactory pdfDocumentFactory,
|
||||
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
||||
@Operation(
|
||||
@@ -49,6 +61,8 @@ public class ConvertHtmlToPDF {
|
||||
originalFilename,
|
||||
bookAndHtmlFormatsInstalled);
|
||||
|
||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||
|
||||
String outputFilename =
|
||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||
+ ".pdf"; // Remove file extension and append .pdf
|
||||
|
||||
@@ -10,28 +10,40 @@ import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.AttributeProvider;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.github.pixee.security.Filenames;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.GeneralFile;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.FileToPdf;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
// Disabled for now
|
||||
// @RestController
|
||||
// @Tag(name = "Convert", description = "Convert APIs")
|
||||
// @RequestMapping("/api/v1/convert")
|
||||
@RestController
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@RequestMapping("/api/v1/convert")
|
||||
public class ConvertMarkdownToPdf {
|
||||
|
||||
// @Autowired
|
||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||
private boolean bookAndHtmlFormatsInstalled;
|
||||
private final boolean bookAndHtmlFormatsInstalled;
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ConvertMarkdownToPdf(
|
||||
CustomPDDocumentFactory pdfDocumentFactory,
|
||||
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
||||
@Operation(
|
||||
@@ -70,7 +82,7 @@ public class ConvertMarkdownToPdf {
|
||||
htmlContent.getBytes(),
|
||||
"converted.html",
|
||||
bookAndHtmlFormatsInstalled);
|
||||
|
||||
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||
String outputFilename =
|
||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||
+ ".pdf"; // Remove file extension and append .pdf
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
@@ -20,13 +21,12 @@ import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
// Disabled for now
|
||||
// @RestController
|
||||
// @Tag(name = "Convert", description = "Convert APIs")
|
||||
// @RequestMapping("/api/v1/convert")
|
||||
public class ConvertPDFToBookController {
|
||||
|
||||
// @Autowired
|
||||
@Autowired
|
||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
||||
private boolean bookAndHtmlFormatsInstalled;
|
||||
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -29,7 +22,6 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
|
||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
@@ -41,13 +33,6 @@ public class ConvertPDFToPDFA {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConvertPDFToPDFA.class);
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
|
||||
@Autowired
|
||||
public ConvertPDFToPDFA(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
|
||||
@Operation(
|
||||
summary = "Convert a PDF to a PDF/A",
|
||||
@@ -61,32 +46,7 @@ public class ConvertPDFToPDFA {
|
||||
// Convert MultipartFile to byte[]
|
||||
byte[] pdfBytes = inputFile.getBytes();
|
||||
|
||||
// Load the PDF document
|
||||
PDDocument document = pdfDocumentFactory.load(pdfBytes);
|
||||
|
||||
// Get the document catalog
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||
|
||||
// Get the AcroForm
|
||||
PDAcroForm acroForm = catalog.getAcroForm();
|
||||
if (acroForm != null) {
|
||||
// Remove signature fields safely
|
||||
List<PDField> fieldsToRemove =
|
||||
acroForm.getFields().stream()
|
||||
.filter(field -> field instanceof PDSignatureField)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!fieldsToRemove.isEmpty()) {
|
||||
acroForm.flatten(fieldsToRemove, false);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
pdfBytes = baos.toByteArray();
|
||||
}
|
||||
}
|
||||
document.close();
|
||||
|
||||
// Save the uploaded (and possibly modified) file to a temporary location
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) {
|
||||
outputStream.write(pdfBytes);
|
||||
@@ -95,28 +55,37 @@ public class ConvertPDFToPDFA {
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
// Prepare the OCRmyPDF command
|
||||
// Prepare the ghostscript command
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("ocrmypdf");
|
||||
command.add("--skip-text");
|
||||
command.add("--tesseract-timeout=0");
|
||||
command.add("--output-type");
|
||||
command.add(outputFormat.toString());
|
||||
command.add(tempInputFile.toString());
|
||||
command.add("gs");
|
||||
command.add("-dPDFA=" + ("pdfa".equals(outputFormat) ? "2" : "1"));
|
||||
command.add("-dNOPAUSE");
|
||||
command.add("-dBATCH");
|
||||
command.add("-sColorConversionStrategy=sRGB");
|
||||
command.add("-sDEVICE=pdfwrite");
|
||||
command.add("-dPDFACompatibilityPolicy=2");
|
||||
command.add("-o");
|
||||
command.add(tempOutputFile.toString());
|
||||
command.add(tempInputFile.toString());
|
||||
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
if (returnCode.getRc() != 0) {
|
||||
logger.info(
|
||||
outputFormat + " conversion failed with return code: " + returnCode.getRc());
|
||||
}
|
||||
|
||||
try {
|
||||
PDDocument doc = pdfDocumentFactory.load(tempOutputFile.toFile());
|
||||
byte[] pdfBytesOutput = Files.readAllBytes(tempOutputFile);
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_PDFA.pdf";
|
||||
return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
pdfBytesOutput, outputFilename, MediaType.APPLICATION_PDF);
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
|
||||
@@ -60,8 +60,6 @@ public class ExtractImagesController {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String format = request.getFormat();
|
||||
boolean allowDuplicates = request.isAllowDuplicates();
|
||||
System.out.println(
|
||||
System.currentTimeMillis() + " file=" + file.getName() + ", format=" + format);
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
|
||||
// Determine if multithreading should be used based on PDF size or number of pages
|
||||
@@ -90,22 +88,35 @@ public class ExtractImagesController {
|
||||
// Iterate over each page
|
||||
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
|
||||
PDPage page = document.getPage(pgNum);
|
||||
int pageNum = document.getPages().indexOf(page) + 1;
|
||||
// Submit a task for processing each page
|
||||
Future<Void> future =
|
||||
executor.submit(
|
||||
() -> {
|
||||
extractImagesFromPage(
|
||||
page,
|
||||
format,
|
||||
filename,
|
||||
pageNum,
|
||||
processedImages,
|
||||
zos,
|
||||
allowDuplicates);
|
||||
return null;
|
||||
// Use the page number directly from the iterator, so no need to
|
||||
// calculate manually
|
||||
int pageNum = document.getPages().indexOf(page) + 1;
|
||||
|
||||
try {
|
||||
// Call the image extraction method for each page
|
||||
extractImagesFromPage(
|
||||
page,
|
||||
format,
|
||||
filename,
|
||||
pageNum,
|
||||
processedImages,
|
||||
zos,
|
||||
allowDuplicates);
|
||||
} catch (IOException e) {
|
||||
// Log the error and continue processing other pages
|
||||
logger.error(
|
||||
"Error extracting images from page {}: {}",
|
||||
pageNum,
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
return null; // Callable requires a return type
|
||||
});
|
||||
|
||||
// Add the Future object to the list to track completion
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,13 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.api.misc.PrintFileRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/misc")
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
@Slf4j
|
||||
public class PrintFileController {
|
||||
|
||||
// TODO
|
||||
@@ -59,7 +61,7 @@ public class PrintFileController {
|
||||
new IllegalArgumentException(
|
||||
"No matching printer found"));
|
||||
|
||||
System.out.println("Selected Printer: " + selectedService.getName());
|
||||
log.info("Selected Printer: " + selectedService.getName());
|
||||
|
||||
if ("application/pdf".equals(contentType)) {
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest;
|
||||
import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/misc")
|
||||
public class ReplaceAndInvertColorController {
|
||||
|
||||
private ReplaceAndInvertColorService replaceAndInvertColorService;
|
||||
|
||||
@Autowired
|
||||
public ReplaceAndInvertColorController(
|
||||
ReplaceAndInvertColorService replaceAndInvertColorService) {
|
||||
this.replaceAndInvertColorService = replaceAndInvertColorService;
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/replace-invert-pdf")
|
||||
@Operation(
|
||||
summary = "Replace-Invert Color PDF",
|
||||
description =
|
||||
"This endpoint accepts a PDF file and option of invert all colors or replace text and background colors. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<InputStreamResource> replaceAndInvertColor(
|
||||
@ModelAttribute ReplaceAndInvertColorRequest replaceAndInvertColorRequest)
|
||||
throws IOException {
|
||||
|
||||
InputStreamResource resource =
|
||||
replaceAndInvertColorService.replaceAndInvertColor(
|
||||
replaceAndInvertColorRequest.getFileInput(),
|
||||
replaceAndInvertColorRequest.getReplaceAndInvertOption(),
|
||||
replaceAndInvertColorRequest.getHighContrastColorCombination(),
|
||||
replaceAndInvertColorRequest.getBackGroundColor(),
|
||||
replaceAndInvertColorRequest.getTextColor());
|
||||
|
||||
// Return the modified PDF as a downloadable file
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=inverted.pdf")
|
||||
.contentType(MediaType.APPLICATION_PDF)
|
||||
.body(resource);
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,6 @@ public class RedactController {
|
||||
float customPadding = request.getCustomPadding();
|
||||
boolean convertPDFToImage = request.isConvertPDFToImage();
|
||||
|
||||
System.out.println(listOfTextString);
|
||||
String[] listOfText = listOfTextString.split("\n");
|
||||
PDDocument document = pdfDocumentFactory.load(file);
|
||||
|
||||
@@ -75,7 +74,6 @@ public class RedactController {
|
||||
|
||||
for (String text : listOfText) {
|
||||
text = text.trim();
|
||||
System.out.println(text);
|
||||
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
||||
List<PDFText> foundTexts = textFinder.getTextLocations(document);
|
||||
redactFoundText(document, foundTexts, customPadding, redactColor);
|
||||
|
||||
@@ -15,7 +15,7 @@ import stirling.software.SPDF.utils.CheckProgramInstall;
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
public class ConverterWebController {
|
||||
|
||||
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
|
||||
@ConditionalOnExpression("${bookAndHtmlFormatsInstalled}")
|
||||
@GetMapping("/book-to-pdf")
|
||||
@Hidden
|
||||
public String convertBookToPdfForm(Model model) {
|
||||
@@ -60,7 +60,7 @@ public class ConverterWebController {
|
||||
|
||||
// PDF TO......
|
||||
|
||||
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
|
||||
@ConditionalOnExpression("${bookAndHtmlFormatsInstalled}")
|
||||
@GetMapping("/pdf-to-book")
|
||||
@Hidden
|
||||
public String convertPdfToBookForm(Model model) {
|
||||
|
||||
@@ -108,6 +108,13 @@ public class GeneralWebController {
|
||||
return "split-pdf-by-sections";
|
||||
}
|
||||
|
||||
@GetMapping("/split-pdf-by-chapters")
|
||||
@Hidden
|
||||
public String splitPdfByChapters(Model model) {
|
||||
model.addAttribute("currentPage", "split-pdf-by-chapters");
|
||||
return "split-pdf-by-chapters";
|
||||
}
|
||||
|
||||
@GetMapping("/view-pdf")
|
||||
@Hidden
|
||||
public String ViewPdfForm2(Model model) {
|
||||
|
||||
@@ -31,6 +31,13 @@ public class OtherWebController {
|
||||
return "misc/compress-pdf";
|
||||
}
|
||||
|
||||
@GetMapping("/replace-and-invert-color-pdf")
|
||||
@Hidden
|
||||
public String replaceAndInvertColorPdfForm(Model model) {
|
||||
model.addAttribute("currentPage", "replace-invert-color-pdf");
|
||||
return "misc/replace-color";
|
||||
}
|
||||
|
||||
@GetMapping("/extract-image-scans")
|
||||
@Hidden
|
||||
public ModelAndView extractImageScansForm() {
|
||||
|
||||
@@ -11,6 +11,11 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
@@ -24,6 +29,7 @@ import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||
@ConfigurationProperties(prefix = "")
|
||||
@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class)
|
||||
@Data
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class ApplicationProperties {
|
||||
|
||||
private Legal legal = new Legal();
|
||||
@@ -57,6 +63,7 @@ public class ApplicationProperties {
|
||||
private Boolean csrfDisabled;
|
||||
private InitialLogin initialLogin = new InitialLogin();
|
||||
private OAUTH2 oauth2 = new OAUTH2();
|
||||
private SAML saml = new SAML();
|
||||
private int loginAttemptCount;
|
||||
private long loginResetTimeMinutes;
|
||||
private String loginMethod = "all";
|
||||
@@ -67,6 +74,34 @@ public class ApplicationProperties {
|
||||
@ToString.Exclude private String password;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SAML {
|
||||
private Boolean enabled = false;
|
||||
private String entityId;
|
||||
private String registrationId;
|
||||
private String spBaseUrl;
|
||||
private String idpMetadataLocation;
|
||||
private KeyStore keystore;
|
||||
|
||||
@Data
|
||||
public static class KeyStore {
|
||||
private String keystoreLocation;
|
||||
private String keystorePassword;
|
||||
private String keyAlias;
|
||||
private String keyPassword;
|
||||
private String realmCertificateAlias;
|
||||
|
||||
public Resource getKeystoreResource() {
|
||||
if (keystoreLocation.startsWith("classpath:")) {
|
||||
return new ClassPathResource(
|
||||
keystoreLocation.substring("classpath:".length()));
|
||||
} else {
|
||||
return new FileSystemResource(keystoreLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class OAUTH2 {
|
||||
private Boolean enabled = false;
|
||||
@@ -136,6 +171,7 @@ public class ApplicationProperties {
|
||||
private boolean customHTMLFiles;
|
||||
private String tessdataDir;
|
||||
private Boolean enableAlphaFunctionality;
|
||||
private String enableAnalytics;
|
||||
}
|
||||
|
||||
@Data
|
||||
@@ -175,11 +211,14 @@ public class ApplicationProperties {
|
||||
@Data
|
||||
public static class AutomaticallyGenerated {
|
||||
@ToString.Exclude private String key;
|
||||
private String UUID;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class EnterpriseEdition {
|
||||
private boolean enabled;
|
||||
@ToString.Exclude private String key;
|
||||
private int maxUsers;
|
||||
private CustomMetadata customMetadata = new CustomMetadata();
|
||||
|
||||
@Data
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package stirling.software.SPDF.model.api.misc;
|
||||
|
||||
public enum HighContrastColorCombination {
|
||||
WHITE_TEXT_ON_BLACK,
|
||||
BLACK_TEXT_ON_WHITE,
|
||||
YELLOW_TEXT_ON_BLACK,
|
||||
GREEN_TEXT_ON_BLACK,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package stirling.software.SPDF.model.api.misc;
|
||||
|
||||
public enum ReplaceAndInvert {
|
||||
HIGH_CONTRAST_COLOR,
|
||||
CUSTOM_COLOR,
|
||||
FULL_INVERSION,
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package stirling.software.SPDF.model.api.misc;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ReplaceAndInvertColorRequest extends PDFFile {
|
||||
|
||||
@Schema(
|
||||
description = "Replace and Invert color options of a pdf.",
|
||||
allowableValues = {"HIGH_CONTRAST_COLOR", "CUSTOM_COLOR", "FULL_INVERSION"})
|
||||
private ReplaceAndInvert replaceAndInvertOption;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"If HIGH_CONTRAST_COLOR option selected, then pick the default color option for text and background.",
|
||||
allowableValues = {
|
||||
"WHITE_TEXT_ON_BLACK",
|
||||
"BLACK_TEXT_ON_WHITE",
|
||||
"YELLOW_TEXT_ON_BLACK",
|
||||
"GREEN_TEXT_ON_BLACK"
|
||||
})
|
||||
private HighContrastColorCombination highContrastColorCombination;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"If CUSTOM_COLOR option selected, then pick the custom color for background. "
|
||||
+ "Expected color value should be 24bit decimal value of a color")
|
||||
private String backGroundColor;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"If CUSTOM_COLOR option selected, then pick the custom color for text. "
|
||||
+ "Expected color value should be 24bit decimal value of a color")
|
||||
private String textColor;
|
||||
}
|
||||
@@ -10,8 +10,10 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.apache.pdfbox.text.TextPosition;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import stirling.software.SPDF.model.PDFText;
|
||||
|
||||
@Slf4j
|
||||
public class TextFinder extends PDFTextStripper {
|
||||
|
||||
private final String searchText;
|
||||
@@ -92,7 +94,7 @@ public class TextFinder extends PDFTextStripper {
|
||||
|
||||
public List<PDFText> getTextLocations(PDDocument document) throws Exception {
|
||||
this.getText(document);
|
||||
System.out.println(
|
||||
log.debug(
|
||||
"Found "
|
||||
+ textOccurrences.size()
|
||||
+ " occurrences of '"
|
||||
|
||||
@@ -7,17 +7,20 @@ import java.io.InputStream;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.config.PdfMetadataService;
|
||||
import stirling.software.SPDF.model.PdfMetadata;
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
|
||||
@Component
|
||||
public class CustomPDDocumentFactory {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CustomPDDocumentFactory.class);
|
||||
|
||||
private final PdfMetadataService pdfMetadataService;
|
||||
|
||||
@Autowired
|
||||
@@ -31,6 +34,36 @@ public class CustomPDDocumentFactory {
|
||||
return document;
|
||||
}
|
||||
|
||||
public byte[] createNewBytesBasedOnOldDocument(byte[] oldDocument) throws IOException {
|
||||
PDDocument document = Loader.loadPDF(oldDocument);
|
||||
return createNewBytesBasedOnOldDocument(document);
|
||||
}
|
||||
|
||||
public byte[] createNewBytesBasedOnOldDocument(File oldDocument) throws IOException {
|
||||
PDDocument document = Loader.loadPDF(oldDocument);
|
||||
return createNewBytesBasedOnOldDocument(document);
|
||||
}
|
||||
|
||||
public byte[] createNewBytesBasedOnOldDocument(PDDocument oldDocument) throws IOException {
|
||||
pdfMetadataService.setMetadataToPdf(
|
||||
oldDocument, pdfMetadataService.extractMetadataFromPdf(oldDocument), true);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
oldDocument.save(baos);
|
||||
oldDocument.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public PDDocument createNewDocumentBasedOnOldDocument(byte[] oldDocument) throws IOException {
|
||||
PDDocument document = Loader.loadPDF(oldDocument);
|
||||
return createNewDocumentBasedOnOldDocument(document);
|
||||
}
|
||||
|
||||
public PDDocument createNewDocumentBasedOnOldDocument(File oldDocument) throws IOException {
|
||||
PDDocument document = Loader.loadPDF(oldDocument);
|
||||
return createNewDocumentBasedOnOldDocument(document);
|
||||
}
|
||||
|
||||
public PDDocument createNewDocumentBasedOnOldDocument(PDDocument oldDocument)
|
||||
throws IOException {
|
||||
PDDocument document = new PDDocument();
|
||||
@@ -71,6 +104,7 @@ public class CustomPDDocumentFactory {
|
||||
public PDDocument load(byte[] input) throws IOException {
|
||||
PDDocument document = Loader.loadPDF(input);
|
||||
pdfMetadataService.setDefaultMetadata(document);
|
||||
removezeropassword(document);
|
||||
return document;
|
||||
}
|
||||
|
||||
@@ -96,5 +130,17 @@ public class CustomPDDocumentFactory {
|
||||
return document;
|
||||
}
|
||||
|
||||
private PDDocument removezeropassword(PDDocument document) throws IOException {
|
||||
if (document.isEncrypted()) {
|
||||
try {
|
||||
logger.info("Removing security from the source document");
|
||||
document.setAllSecurityToBeRemoved(true);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Cannot decrypt the pdf");
|
||||
}
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
// Add other load methods as needed, following the same pattern
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.search.Search;
|
||||
|
||||
@Service
|
||||
public class MetricsAggregatorService {
|
||||
|
||||
private final MeterRegistry meterRegistry;
|
||||
private final PostHogService postHogService;
|
||||
private final Map<String, Double> lastSentMetrics = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
public MetricsAggregatorService(MeterRegistry meterRegistry, PostHogService postHogService) {
|
||||
this.meterRegistry = meterRegistry;
|
||||
this.postHogService = postHogService;
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 900000) // Run every 15 minutes
|
||||
public void aggregateAndSendMetrics() {
|
||||
Map<String, Object> metrics = new HashMap<>();
|
||||
Search.in(meterRegistry)
|
||||
.name("http.requests")
|
||||
.counters()
|
||||
.forEach(
|
||||
counter -> {
|
||||
String key =
|
||||
String.format(
|
||||
"http_requests_%s_%s",
|
||||
counter.getId().getTag("method"),
|
||||
counter.getId().getTag("uri").replace("/", "_"));
|
||||
|
||||
double currentCount = counter.count();
|
||||
double lastCount = lastSentMetrics.getOrDefault(key, 0.0);
|
||||
double difference = currentCount - lastCount;
|
||||
|
||||
if (difference > 0) {
|
||||
metrics.put(key, difference);
|
||||
lastSentMetrics.put(key, currentCount);
|
||||
}
|
||||
});
|
||||
|
||||
// Send aggregated metrics to PostHog
|
||||
if (!metrics.isEmpty()) {
|
||||
postHogService.captureEvent("aggregated_metrics", metrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package stirling.software.SPDF.config;
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
@@ -15,16 +15,16 @@ import stirling.software.SPDF.model.PdfMetadata;
|
||||
public class PdfMetadataService {
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
private final String appVersion;
|
||||
private final String stirlingPDFLabel;
|
||||
private final UserServiceInterface userService;
|
||||
|
||||
@Autowired
|
||||
public PdfMetadataService(
|
||||
ApplicationProperties applicationProperties,
|
||||
@Qualifier("appVersion") String appVersion,
|
||||
@Qualifier("StirlingPDFLabel") String stirlingPDFLabel,
|
||||
@Autowired(required = false) UserServiceInterface userService) {
|
||||
this.applicationProperties = applicationProperties;
|
||||
this.appVersion = appVersion;
|
||||
this.stirlingPDFLabel = stirlingPDFLabel;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@@ -59,51 +59,40 @@ public class PdfMetadataService {
|
||||
|
||||
private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
|
||||
|
||||
String creator = "Stirling-PDF";
|
||||
String creator = stirlingPDFLabel;
|
||||
|
||||
// if (applicationProperties
|
||||
// .getEnterpriseEdition()
|
||||
// .getCustomMetadata()
|
||||
// .isAutoUpdateMetadata()) {
|
||||
if (applicationProperties
|
||||
.getEnterpriseEdition()
|
||||
.getCustomMetadata()
|
||||
.isAutoUpdateMetadata()) {
|
||||
|
||||
// producer =
|
||||
//
|
||||
// applicationProperties.getEnterpriseEdition().getCustomMetadata().getProducer();
|
||||
// creator =
|
||||
// applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
|
||||
// title = applicationProperties.getEnterpriseEdition().getCustomMetadata().getTitle();
|
||||
creator = applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
|
||||
pdf.getDocumentInformation().setProducer(stirlingPDFLabel);
|
||||
}
|
||||
|
||||
// if ("{filename}".equals(title)) {
|
||||
// title = "Filename"; // Replace with actual filename logic
|
||||
// } else if ("{unchanged}".equals(title)) {
|
||||
// title = pdfMetadata.getTitle(); // Keep the original title
|
||||
// }
|
||||
// }
|
||||
|
||||
pdf.getDocumentInformation().setCreator(creator + " " + appVersion);
|
||||
pdf.getDocumentInformation().setCreator(creator);
|
||||
pdf.getDocumentInformation().setCreationDate(Calendar.getInstance());
|
||||
}
|
||||
|
||||
private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
|
||||
String producer = "Stirling-PDF";
|
||||
String title = pdfMetadata.getTitle();
|
||||
pdf.getDocumentInformation().setTitle(title);
|
||||
pdf.getDocumentInformation().setProducer(producer + " " + appVersion);
|
||||
pdf.getDocumentInformation().setProducer(stirlingPDFLabel);
|
||||
pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());
|
||||
pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());
|
||||
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
|
||||
|
||||
String author = pdfMetadata.getAuthor();
|
||||
// if (applicationProperties
|
||||
// .getEnterpriseEdition()
|
||||
// .getCustomMetadata()
|
||||
// .isAutoUpdateMetadata()) {
|
||||
// author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
|
||||
if (applicationProperties
|
||||
.getEnterpriseEdition()
|
||||
.getCustomMetadata()
|
||||
.isAutoUpdateMetadata()) {
|
||||
author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
|
||||
|
||||
// if (userService != null) {
|
||||
// author = author.replace("username", userService.getCurrentUsername());
|
||||
// }
|
||||
// }
|
||||
if (userService != null) {
|
||||
author = author.replace("username", userService.getCurrentUsername());
|
||||
}
|
||||
}
|
||||
pdf.getDocumentInformation().setAuthor(author);
|
||||
}
|
||||
}
|
||||
379
src/main/java/stirling/software/SPDF/service/PostHogService.java
Normal file
379
src/main/java/stirling/software/SPDF/service/PostHogService.java
Normal file
@@ -0,0 +1,379 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.management.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.posthog.java.PostHog;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Service
|
||||
public class PostHogService {
|
||||
private final PostHog postHog;
|
||||
private final String uniqueId;
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired
|
||||
public PostHogService(
|
||||
PostHog postHog,
|
||||
@Qualifier("UUID") String uuid,
|
||||
ApplicationProperties applicationProperties) {
|
||||
this.postHog = postHog;
|
||||
this.uniqueId = uuid;
|
||||
this.applicationProperties = applicationProperties;
|
||||
captureSystemInfo();
|
||||
}
|
||||
|
||||
private void captureSystemInfo() {
|
||||
if (!Boolean.getBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
postHog.capture(uniqueId, "system_info_captured", captureServerMetrics());
|
||||
} catch (Exception e) {
|
||||
// Handle exceptions
|
||||
}
|
||||
}
|
||||
|
||||
public void captureEvent(String eventName, Map<String, Object> properties) {
|
||||
if (!Boolean.getBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||
return;
|
||||
}
|
||||
postHog.capture(uniqueId, eventName, properties);
|
||||
}
|
||||
|
||||
public Map<String, Object> captureServerMetrics() {
|
||||
Map<String, Object> metrics = new HashMap<>();
|
||||
|
||||
try {
|
||||
// System info
|
||||
metrics.put("os_name", System.getProperty("os.name"));
|
||||
metrics.put("os_version", System.getProperty("os.version"));
|
||||
metrics.put("java_version", System.getProperty("java.version"));
|
||||
metrics.put("user_name", System.getProperty("user.name"));
|
||||
metrics.put("user_home", System.getProperty("user.home"));
|
||||
metrics.put("user_dir", System.getProperty("user.dir"));
|
||||
|
||||
// CPU and Memory
|
||||
metrics.put("cpu_cores", Runtime.getRuntime().availableProcessors());
|
||||
metrics.put("total_memory", Runtime.getRuntime().totalMemory());
|
||||
metrics.put("free_memory", Runtime.getRuntime().freeMemory());
|
||||
|
||||
// Network and Server Identity
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
metrics.put("ip_address", localHost.getHostAddress());
|
||||
metrics.put("hostname", localHost.getHostName());
|
||||
metrics.put("mac_address", getMacAddress());
|
||||
|
||||
// JVM info
|
||||
metrics.put("jvm_vendor", System.getProperty("java.vendor"));
|
||||
metrics.put("jvm_version", System.getProperty("java.vm.version"));
|
||||
|
||||
// Locale and Timezone
|
||||
metrics.put("system_language", System.getProperty("user.language"));
|
||||
metrics.put("system_country", System.getProperty("user.country"));
|
||||
metrics.put("timezone", TimeZone.getDefault().getID());
|
||||
metrics.put("locale", Locale.getDefault().toString());
|
||||
|
||||
// Disk info
|
||||
File root = new File(".");
|
||||
metrics.put("total_disk_space", root.getTotalSpace());
|
||||
metrics.put("free_disk_space", root.getFreeSpace());
|
||||
|
||||
// Process info
|
||||
metrics.put("process_id", ProcessHandle.current().pid());
|
||||
|
||||
// JVM metrics
|
||||
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
|
||||
metrics.put("jvm_uptime_ms", runtimeMXBean.getUptime());
|
||||
metrics.put("jvm_start_time", runtimeMXBean.getStartTime());
|
||||
|
||||
// Memory metrics
|
||||
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
|
||||
metrics.put("heap_memory_usage", memoryMXBean.getHeapMemoryUsage().getUsed());
|
||||
metrics.put("non_heap_memory_usage", memoryMXBean.getNonHeapMemoryUsage().getUsed());
|
||||
|
||||
// CPU metrics
|
||||
OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();
|
||||
metrics.put("system_load_average", osMXBean.getSystemLoadAverage());
|
||||
|
||||
// Thread metrics
|
||||
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
|
||||
metrics.put("thread_count", threadMXBean.getThreadCount());
|
||||
metrics.put("daemon_thread_count", threadMXBean.getDaemonThreadCount());
|
||||
metrics.put("peak_thread_count", threadMXBean.getPeakThreadCount());
|
||||
|
||||
// Garbage collection metrics
|
||||
for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
|
||||
metrics.put("gc_" + gcBean.getName() + "_count", gcBean.getCollectionCount());
|
||||
metrics.put("gc_" + gcBean.getName() + "_time", gcBean.getCollectionTime());
|
||||
}
|
||||
|
||||
// Network interfaces
|
||||
metrics.put("network_interfaces", getNetworkInterfacesInfo());
|
||||
|
||||
// Docker detection and stats
|
||||
boolean isDocker = isRunningInDocker();
|
||||
metrics.put("is_docker", isDocker);
|
||||
if (isDocker) {
|
||||
metrics.put("docker_metrics", getDockerMetrics());
|
||||
}
|
||||
metrics.put("application_properties", captureApplicationProperties());
|
||||
|
||||
} catch (Exception e) {
|
||||
metrics.put("error", e.getMessage());
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
private boolean isRunningInDocker() {
|
||||
return Files.exists(Paths.get("/.dockerenv"));
|
||||
}
|
||||
|
||||
private Map<String, Object> getDockerMetrics() {
|
||||
Map<String, Object> dockerMetrics = new HashMap<>();
|
||||
|
||||
// Network-related Docker info
|
||||
dockerMetrics.put("docker_network_mode", System.getenv("DOCKER_NETWORK_MODE"));
|
||||
|
||||
// Container name (if set)
|
||||
String containerName = System.getenv("CONTAINER_NAME");
|
||||
if (containerName != null && !containerName.isEmpty()) {
|
||||
dockerMetrics.put("container_name", containerName);
|
||||
}
|
||||
|
||||
// Docker compose information
|
||||
String composeProject = System.getenv("COMPOSE_PROJECT_NAME");
|
||||
String composeService = System.getenv("COMPOSE_SERVICE_NAME");
|
||||
if (composeProject != null && composeService != null) {
|
||||
dockerMetrics.put("compose_project", composeProject);
|
||||
dockerMetrics.put("compose_service", composeService);
|
||||
}
|
||||
|
||||
// Kubernetes-specific info (if running in K8s)
|
||||
String k8sPodName = System.getenv("KUBERNETES_POD_NAME");
|
||||
if (k8sPodName != null) {
|
||||
dockerMetrics.put("k8s_pod_name", k8sPodName);
|
||||
dockerMetrics.put("k8s_namespace", System.getenv("KUBERNETES_NAMESPACE"));
|
||||
dockerMetrics.put("k8s_node_name", System.getenv("KUBERNETES_NODE_NAME"));
|
||||
}
|
||||
|
||||
// New environment variables
|
||||
dockerMetrics.put("version_tag", System.getenv("VERSION_TAG"));
|
||||
dockerMetrics.put("docker_enable_security", System.getenv("DOCKER_ENABLE_SECURITY"));
|
||||
dockerMetrics.put("fat_docker", System.getenv("FAT_DOCKER"));
|
||||
|
||||
return dockerMetrics;
|
||||
}
|
||||
|
||||
private void addIfNotEmpty(Map<String, Object> map, String key, Object value) {
|
||||
if (value != null) {
|
||||
if (value instanceof String) {
|
||||
String strValue = (String) value;
|
||||
if (!StringUtils.isBlank(strValue)) {
|
||||
map.put(key, strValue.trim());
|
||||
}
|
||||
} else {
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> captureApplicationProperties() {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
// Capture Legal properties
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"legal_termsAndConditions",
|
||||
applicationProperties.getLegal().getTermsAndConditions());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"legal_privacyPolicy",
|
||||
applicationProperties.getLegal().getPrivacyPolicy());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"legal_accessibilityStatement",
|
||||
applicationProperties.getLegal().getAccessibilityStatement());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"legal_cookiePolicy",
|
||||
applicationProperties.getLegal().getCookiePolicy());
|
||||
addIfNotEmpty(
|
||||
properties, "legal_impressum", applicationProperties.getLegal().getImpressum());
|
||||
|
||||
// Capture Security properties
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_enableLogin",
|
||||
applicationProperties.getSecurity().getEnableLogin());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_csrfDisabled",
|
||||
applicationProperties.getSecurity().getCsrfDisabled());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_loginAttemptCount",
|
||||
applicationProperties.getSecurity().getLoginAttemptCount());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_loginResetTimeMinutes",
|
||||
applicationProperties.getSecurity().getLoginResetTimeMinutes());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_loginMethod",
|
||||
applicationProperties.getSecurity().getLoginMethod());
|
||||
|
||||
// Capture OAuth2 properties (excluding sensitive information)
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_oauth2_enabled",
|
||||
applicationProperties.getSecurity().getOauth2().getEnabled());
|
||||
if (applicationProperties.getSecurity().getOauth2().getEnabled()) {
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_oauth2_autoCreateUser",
|
||||
applicationProperties.getSecurity().getOauth2().getAutoCreateUser());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_oauth2_blockRegistration",
|
||||
applicationProperties.getSecurity().getOauth2().getBlockRegistration());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_oauth2_useAsUsername",
|
||||
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_oauth2_provider",
|
||||
applicationProperties.getSecurity().getOauth2().getProvider());
|
||||
}
|
||||
// Capture System properties
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"system_defaultLocale",
|
||||
applicationProperties.getSystem().getDefaultLocale());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"system_googlevisibility",
|
||||
applicationProperties.getSystem().getGooglevisibility());
|
||||
addIfNotEmpty(
|
||||
properties, "system_showUpdate", applicationProperties.getSystem().isShowUpdate());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"system_showUpdateOnlyAdmin",
|
||||
applicationProperties.getSystem().getShowUpdateOnlyAdmin());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"system_customHTMLFiles",
|
||||
applicationProperties.getSystem().isCustomHTMLFiles());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"system_tessdataDir",
|
||||
applicationProperties.getSystem().getTessdataDir());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"system_enableAlphaFunctionality",
|
||||
applicationProperties.getSystem().getEnableAlphaFunctionality());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"system_enableAnalytics",
|
||||
applicationProperties.getSystem().getEnableAnalytics());
|
||||
|
||||
// Capture UI properties
|
||||
addIfNotEmpty(properties, "ui_appName", applicationProperties.getUi().getAppName());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"ui_homeDescription",
|
||||
applicationProperties.getUi().getHomeDescription());
|
||||
addIfNotEmpty(
|
||||
properties, "ui_appNameNavbar", applicationProperties.getUi().getAppNameNavbar());
|
||||
|
||||
// Capture Metrics properties
|
||||
addIfNotEmpty(
|
||||
properties, "metrics_enabled", applicationProperties.getMetrics().getEnabled());
|
||||
|
||||
// Capture EnterpriseEdition properties
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"enterpriseEdition_enabled",
|
||||
applicationProperties.getEnterpriseEdition().isEnabled());
|
||||
if (applicationProperties.getEnterpriseEdition().isEnabled()) {
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"enterpriseEdition_customMetadata_autoUpdateMetadata",
|
||||
applicationProperties
|
||||
.getEnterpriseEdition()
|
||||
.getCustomMetadata()
|
||||
.isAutoUpdateMetadata());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"enterpriseEdition_customMetadata_author",
|
||||
applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"enterpriseEdition_customMetadata_creator",
|
||||
applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"enterpriseEdition_customMetadata_producer",
|
||||
applicationProperties.getEnterpriseEdition().getCustomMetadata().getProducer());
|
||||
}
|
||||
// Capture AutoPipeline properties
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"autoPipeline_outputFolder",
|
||||
applicationProperties.getAutoPipeline().getOutputFolder());
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private String getMacAddress() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> networkInterfaces =
|
||||
NetworkInterface.getNetworkInterfaces();
|
||||
while (networkInterfaces.hasMoreElements()) {
|
||||
NetworkInterface ni = networkInterfaces.nextElement();
|
||||
byte[] hardwareAddress = ni.getHardwareAddress();
|
||||
if (hardwareAddress != null) {
|
||||
String[] hexadecimal = new String[hardwareAddress.length];
|
||||
for (int i = 0; i < hardwareAddress.length; i++) {
|
||||
hexadecimal[i] = String.format("%02X", hardwareAddress[i]);
|
||||
}
|
||||
return String.join("-", hexadecimal);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Handle exception
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
private Map<String, String> getNetworkInterfacesInfo() {
|
||||
Map<String, String> interfacesInfo = new HashMap<>();
|
||||
try {
|
||||
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
|
||||
while (nets.hasMoreElements()) {
|
||||
NetworkInterface netint = nets.nextElement();
|
||||
interfacesInfo.put(netint.getName(), netint.getDisplayName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
interfacesInfo.put("error", e.getMessage());
|
||||
}
|
||||
return interfacesInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package stirling.software.SPDF.service.misc;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.Factories.ReplaceAndInvertColorFactory;
|
||||
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
import stirling.software.SPDF.utils.misc.ReplaceAndInvertColorStrategy;
|
||||
|
||||
@Service
|
||||
public class ReplaceAndInvertColorService {
|
||||
private ReplaceAndInvertColorFactory replaceAndInvertColorFactory;
|
||||
|
||||
@Autowired
|
||||
public ReplaceAndInvertColorService(ReplaceAndInvertColorFactory replaceAndInvertColorFactory) {
|
||||
this.replaceAndInvertColorFactory = replaceAndInvertColorFactory;
|
||||
}
|
||||
|
||||
public InputStreamResource replaceAndInvertColor(
|
||||
MultipartFile file,
|
||||
ReplaceAndInvert replaceAndInvertOption,
|
||||
HighContrastColorCombination highContrastColorCombination,
|
||||
String backGroundColor,
|
||||
String textColor)
|
||||
throws IOException {
|
||||
|
||||
ReplaceAndInvertColorStrategy replaceColorStrategy =
|
||||
replaceAndInvertColorFactory.replaceAndInvert(
|
||||
file,
|
||||
replaceAndInvertOption,
|
||||
highContrastColorCombination,
|
||||
backGroundColor,
|
||||
textColor);
|
||||
|
||||
return replaceColorStrategy.replace();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package stirling.software.SPDF.utils;
|
||||
|
||||
import org.owasp.html.HtmlPolicyBuilder;
|
||||
import org.owasp.html.PolicyFactory;
|
||||
import org.owasp.html.Sanitizers;
|
||||
|
||||
public class CustomHtmlSanitizer {
|
||||
private static final PolicyFactory POLICY =
|
||||
Sanitizers.FORMATTING
|
||||
.and(Sanitizers.BLOCKS)
|
||||
.and(Sanitizers.STYLES)
|
||||
.and(Sanitizers.LINKS)
|
||||
.and(Sanitizers.TABLES)
|
||||
.and(Sanitizers.IMAGES)
|
||||
.and(new HtmlPolicyBuilder().disallowElements("noscript").toFactory());
|
||||
|
||||
public static String sanitize(String html) {
|
||||
String htmlAfter = POLICY.sanitize(html);
|
||||
return htmlAfter;
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,23 @@ package stirling.software.SPDF.utils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import io.github.pixee.security.ZipSecurity;
|
||||
|
||||
@@ -33,19 +40,25 @@ public class FileToPdf {
|
||||
try {
|
||||
if (fileName.endsWith(".html")) {
|
||||
tempInputFile = Files.createTempFile("input_", ".html");
|
||||
Files.write(tempInputFile, fileBytes);
|
||||
} else {
|
||||
String sanitizedHtml =
|
||||
sanitizeHtmlContent(new String(fileBytes, StandardCharsets.UTF_8));
|
||||
Files.write(tempInputFile, sanitizedHtml.getBytes(StandardCharsets.UTF_8));
|
||||
} else if (fileName.endsWith(".zip")) {
|
||||
tempInputFile = Files.createTempFile("input_", ".zip");
|
||||
Files.write(tempInputFile, fileBytes);
|
||||
sanitizeHtmlFilesInZip(tempInputFile);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported file format: " + fileName);
|
||||
}
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
if (!htmlFormatsInstalled) {
|
||||
command.add("weasyprint");
|
||||
command.add("-e utf-8");
|
||||
command.add("-e");
|
||||
command.add("utf-8");
|
||||
command.add("-v");
|
||||
command.add(tempInputFile.toString());
|
||||
command.add(tempOutputFile.toString());
|
||||
|
||||
} else {
|
||||
command.add("ebook-convert");
|
||||
command.add(tempInputFile.toString());
|
||||
@@ -54,10 +67,8 @@ public class FileToPdf {
|
||||
command.add("a4");
|
||||
|
||||
if (request != null && request.getZoom() != 1.0) {
|
||||
// Create a temporary CSS file
|
||||
File tempCssFile = Files.createTempFile("customStyle", ".css").toFile();
|
||||
try (FileWriter writer = new FileWriter(tempCssFile)) {
|
||||
// Write the CSS rule to the file
|
||||
writer.write("body { zoom: " + request.getZoom() + "; }");
|
||||
}
|
||||
command.add("--extra-css");
|
||||
@@ -65,9 +76,7 @@ public class FileToPdf {
|
||||
}
|
||||
}
|
||||
|
||||
ProcessExecutorResult returnCode;
|
||||
|
||||
returnCode =
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||
.runCommandWithOutputHandling(command);
|
||||
|
||||
@@ -78,8 +87,6 @@ public class FileToPdf {
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
|
||||
// Clean up temporary files
|
||||
Files.deleteIfExists(tempOutputFile);
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
}
|
||||
@@ -87,6 +94,81 @@ public class FileToPdf {
|
||||
return pdfBytes;
|
||||
}
|
||||
|
||||
private static String sanitizeHtmlContent(String htmlContent) {
|
||||
return CustomHtmlSanitizer.sanitize(htmlContent);
|
||||
}
|
||||
|
||||
private static void sanitizeHtmlFilesInZip(Path zipFilePath) throws IOException {
|
||||
Path tempUnzippedDir = Files.createTempDirectory("unzipped_");
|
||||
try (ZipInputStream zipIn =
|
||||
ZipSecurity.createHardenedInputStream(
|
||||
new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) {
|
||||
ZipEntry entry = zipIn.getNextEntry();
|
||||
while (entry != null) {
|
||||
Path filePath = tempUnzippedDir.resolve(entry.getName());
|
||||
if (!entry.isDirectory()) {
|
||||
Files.createDirectories(filePath.getParent());
|
||||
if (entry.getName().toLowerCase().endsWith(".html")
|
||||
|| entry.getName().toLowerCase().endsWith(".htm")) {
|
||||
String content = new String(zipIn.readAllBytes(), StandardCharsets.UTF_8);
|
||||
String sanitizedContent = sanitizeHtmlContent(content);
|
||||
Files.write(filePath, sanitizedContent.getBytes(StandardCharsets.UTF_8));
|
||||
} else {
|
||||
Files.copy(zipIn, filePath);
|
||||
}
|
||||
}
|
||||
zipIn.closeEntry();
|
||||
entry = zipIn.getNextEntry();
|
||||
}
|
||||
}
|
||||
|
||||
// Repack the sanitized files
|
||||
zipDirectory(tempUnzippedDir, zipFilePath);
|
||||
|
||||
// Clean up
|
||||
deleteDirectory(tempUnzippedDir);
|
||||
}
|
||||
|
||||
private static void zipDirectory(Path sourceDir, Path zipFilePath) throws IOException {
|
||||
try (ZipOutputStream zos =
|
||||
new ZipOutputStream(new FileOutputStream(zipFilePath.toFile()))) {
|
||||
Files.walk(sourceDir)
|
||||
.filter(path -> !Files.isDirectory(path))
|
||||
.forEach(
|
||||
path -> {
|
||||
ZipEntry zipEntry =
|
||||
new ZipEntry(sourceDir.relativize(path).toString());
|
||||
try {
|
||||
zos.putNextEntry(zipEntry);
|
||||
Files.copy(path, zos);
|
||||
zos.closeEntry();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteDirectory(Path dir) throws IOException {
|
||||
Files.walkFileTree(
|
||||
dir,
|
||||
new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
||||
throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
|
||||
throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Path unzipAndGetMainHtml(byte[] fileBytes) throws IOException {
|
||||
Path tempDirectory = Files.createTempDirectory("unzipped_");
|
||||
try (ZipInputStream zipIn =
|
||||
|
||||
@@ -5,18 +5,28 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.simpleyaml.configuration.file.YamlFile;
|
||||
import org.simpleyaml.configuration.file.YamlFileWrapper;
|
||||
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -262,4 +272,81 @@ public class GeneralUtils {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isValidUUID(String uuid) {
|
||||
if (uuid == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
UUID.fromString(uuid);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveKeyToConfig(String id, String key) throws IOException {
|
||||
saveKeyToConfig(id, key, true);
|
||||
}
|
||||
|
||||
public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
|
||||
throws IOException {
|
||||
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||
|
||||
final YamlFile settingsYml = new YamlFile(path.toFile());
|
||||
DumperOptions yamlOptionssettingsYml =
|
||||
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
|
||||
yamlOptionssettingsYml.setSplitLines(false);
|
||||
|
||||
settingsYml.loadWithComments();
|
||||
|
||||
YamlFileWrapper writer = settingsYml.path(id).set(key);
|
||||
if (autoGenerated) {
|
||||
writer.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||
}
|
||||
settingsYml.save();
|
||||
}
|
||||
|
||||
public static String generateMachineFingerprint() {
|
||||
try {
|
||||
// Get the MAC address
|
||||
StringBuilder sb = new StringBuilder();
|
||||
InetAddress ip = InetAddress.getLocalHost();
|
||||
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
|
||||
|
||||
if (network == null) {
|
||||
Enumeration<NetworkInterface> networks = NetworkInterface.getNetworkInterfaces();
|
||||
while (networks.hasMoreElements()) {
|
||||
NetworkInterface net = networks.nextElement();
|
||||
byte[] mac = net.getHardwareAddress();
|
||||
if (mac != null) {
|
||||
for (int i = 0; i < mac.length; i++) {
|
||||
sb.append(String.format("%02X", mac[i]));
|
||||
}
|
||||
break; // Use the first network interface with a MAC address
|
||||
}
|
||||
}
|
||||
} else {
|
||||
byte[] mac = network.getHardwareAddress();
|
||||
if (mac != null) {
|
||||
for (int i = 0; i < mac.length; i++) {
|
||||
sb.append(String.format("%02X", mac[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hash the MAC address for privacy and consistency
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder fingerprint = new StringBuilder();
|
||||
for (byte b : hash) {
|
||||
fingerprint.append(String.format("%02x", b));
|
||||
}
|
||||
|
||||
return fingerprint.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
return "GenericID";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,6 @@ public class PDFToFile {
|
||||
Files.deleteIfExists(tempInputFile);
|
||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||
}
|
||||
System.out.println("fileBytes=" + fileBytes.length);
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ public class RequestUriUtils {
|
||||
|| requestURI.startsWith(contextPath + "/public/")
|
||||
|| requestURI.startsWith(contextPath + "/pdfjs/")
|
||||
|| requestURI.startsWith(contextPath + "/login")
|
||||
|| requestURI.startsWith(contextPath + "/error")
|
||||
|| requestURI.endsWith(".svg")
|
||||
|| requestURI.endsWith(".png")
|
||||
|| requestURI.endsWith(".ico")
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package stirling.software.SPDF.utils.misc;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||
import org.apache.pdfbox.pdmodel.font.*;
|
||||
import org.apache.pdfbox.text.TextPosition;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
|
||||
public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy {
|
||||
|
||||
private String textColor;
|
||||
private String backgroundColor;
|
||||
private HighContrastColorCombination highContrastColorCombination;
|
||||
|
||||
public CustomColorReplaceStrategy(
|
||||
MultipartFile file,
|
||||
ReplaceAndInvert replaceAndInvert,
|
||||
String textColor,
|
||||
String backgroundColor,
|
||||
HighContrastColorCombination highContrastColorCombination) {
|
||||
super(file, replaceAndInvert);
|
||||
this.textColor = textColor;
|
||||
this.backgroundColor = backgroundColor;
|
||||
this.highContrastColorCombination = highContrastColorCombination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStreamResource replace() throws IOException {
|
||||
|
||||
// If ReplaceAndInvert is HighContrastColor option, then get the colors of text and
|
||||
// background from static
|
||||
if (replaceAndInvert == ReplaceAndInvert.HIGH_CONTRAST_COLOR) {
|
||||
String[] colors =
|
||||
HighContrastColorReplaceDecider.getColors(
|
||||
replaceAndInvert, highContrastColorCombination);
|
||||
this.textColor = colors[0];
|
||||
this.backgroundColor = colors[1];
|
||||
}
|
||||
|
||||
// Create a temporary file, with the original filename from the multipart file
|
||||
File file = File.createTempFile("temp", getFileInput().getOriginalFilename());
|
||||
|
||||
// Transfer the content of the multipart file to the file
|
||||
getFileInput().transferTo(file);
|
||||
|
||||
try (PDDocument document = Loader.loadPDF(file)) {
|
||||
|
||||
PDPageTree pages = document.getPages();
|
||||
|
||||
for (PDPage page : pages) {
|
||||
|
||||
PdfTextStripperCustom pdfTextStripperCustom = new PdfTextStripperCustom();
|
||||
// Get text positions
|
||||
List<List<TextPosition>> charactersByArticle =
|
||||
pdfTextStripperCustom.processPageCustom(page);
|
||||
|
||||
// Begin a new content stream
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||
|
||||
// Set the new text color
|
||||
contentStream.setNonStrokingColor(Color.decode(this.textColor));
|
||||
|
||||
// Draw the text with the new color
|
||||
for (List<TextPosition> textPositions : charactersByArticle) {
|
||||
for (TextPosition text : textPositions) {
|
||||
// Move to the text position
|
||||
contentStream.beginText();
|
||||
contentStream.newLineAtOffset(
|
||||
text.getX(), page.getMediaBox().getHeight() - text.getY());
|
||||
PDFont font = null;
|
||||
String unicodeText = text.getUnicode();
|
||||
try {
|
||||
font = PDFontFactory.createFont(text.getFont().getCOSObject());
|
||||
} catch (IOException io) {
|
||||
System.out.println("Primary font not found, using fallback font.");
|
||||
font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||
}
|
||||
// if a character is not supported by font, then look for supported font
|
||||
try {
|
||||
byte[] bytes = font.encode(unicodeText);
|
||||
} catch (IOException io) {
|
||||
System.out.println("text could not be encoded ");
|
||||
font = checkSupportedFontForCharacter(unicodeText);
|
||||
} catch (IllegalArgumentException ie) {
|
||||
System.out.println("text not supported by font ");
|
||||
font = checkSupportedFontForCharacter(unicodeText);
|
||||
} finally {
|
||||
// if any other font is not supported, then replace default character *
|
||||
if (font == null) {
|
||||
font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||
unicodeText = "*";
|
||||
}
|
||||
}
|
||||
contentStream.setFont(font, text.getFontSize());
|
||||
contentStream.showText(unicodeText);
|
||||
contentStream.endText();
|
||||
}
|
||||
}
|
||||
// Close the content stream
|
||||
contentStream.close();
|
||||
// Use a content stream to overlay the background color
|
||||
try (PDPageContentStream contentStreamBg =
|
||||
new PDPageContentStream(
|
||||
document,
|
||||
page,
|
||||
PDPageContentStream.AppendMode.PREPEND,
|
||||
true,
|
||||
true)) {
|
||||
// Set background color (e.g., light yellow)
|
||||
contentStreamBg.setNonStrokingColor(Color.decode(this.backgroundColor));
|
||||
contentStreamBg.addRect(
|
||||
0, 0, page.getMediaBox().getWidth(), page.getMediaBox().getHeight());
|
||||
contentStreamBg.fill();
|
||||
}
|
||||
}
|
||||
// Save the modified PDF to a ByteArrayOutputStream
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
document.save(byteArrayOutputStream);
|
||||
document.close();
|
||||
|
||||
// Prepare the modified PDF for download
|
||||
ByteArrayInputStream inputStream =
|
||||
new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||
InputStreamResource resource = new InputStreamResource(inputStream);
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
private PDFont checkSupportedFontForCharacter(String unicodeText) {
|
||||
|
||||
Set<String> fonts = Standard14Fonts.getNames();
|
||||
for (String font : fonts) {
|
||||
Standard14Fonts.FontName fontName = Standard14Fonts.getMappedFontName(font);
|
||||
PDFont currentFont = new PDType1Font(fontName);
|
||||
try {
|
||||
byte[] bytes = currentFont.encode(unicodeText);
|
||||
return currentFont;
|
||||
} catch (IOException io) {
|
||||
System.out.println("text could not be encoded ");
|
||||
} catch (IllegalArgumentException ie) {
|
||||
System.out.println("text not supported by font ");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package stirling.software.SPDF.utils.misc;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
|
||||
public class HighContrastColorReplaceDecider {
|
||||
|
||||
// To decide the text and background colors for High contrast color option for replace-invert
|
||||
// color feature
|
||||
public static String[] getColors(
|
||||
ReplaceAndInvert replaceAndInvert,
|
||||
HighContrastColorCombination highContrastColorCombination) {
|
||||
|
||||
if (highContrastColorCombination == HighContrastColorCombination.BLACK_TEXT_ON_WHITE) {
|
||||
return new String[] {"0", "16777215"};
|
||||
} else if (highContrastColorCombination
|
||||
== HighContrastColorCombination.GREEN_TEXT_ON_BLACK) {
|
||||
return new String[] {"65280", "0"};
|
||||
} else if (highContrastColorCombination
|
||||
== HighContrastColorCombination.WHITE_TEXT_ON_BLACK) {
|
||||
return new String[] {"16777215", "0"};
|
||||
} else if (highContrastColorCombination
|
||||
== HighContrastColorCombination.YELLOW_TEXT_ON_BLACK) {
|
||||
|
||||
return new String[] {"16776960", "0"};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package stirling.software.SPDF.utils.misc;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
|
||||
public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy {
|
||||
|
||||
public InvertFullColorStrategy(MultipartFile file, ReplaceAndInvert replaceAndInvert) {
|
||||
super(file, replaceAndInvert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStreamResource replace() throws IOException {
|
||||
|
||||
// Create a temporary file, with the original filename from the multipart file
|
||||
File file = File.createTempFile("temp", getFileInput().getOriginalFilename());
|
||||
|
||||
// Transfer the content of the multipart file to the file
|
||||
getFileInput().transferTo(file);
|
||||
|
||||
// Load the uploaded PDF
|
||||
PDDocument document = Loader.loadPDF(file);
|
||||
|
||||
// Render each page and invert colors
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
for (int page = 0; page < document.getNumberOfPages(); page++) {
|
||||
BufferedImage image =
|
||||
pdfRenderer.renderImageWithDPI(page, 300); // Render page at 300 DPI
|
||||
|
||||
// Invert the colors
|
||||
invertImageColors(image);
|
||||
|
||||
// Create a new PDPage from the inverted image
|
||||
PDPage pdPage = document.getPage(page);
|
||||
PDImageXObject pdImage =
|
||||
PDImageXObject.createFromFileByContent(
|
||||
convertToBufferedImageTpFile(image), document);
|
||||
|
||||
PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, pdPage, PDPageContentStream.AppendMode.OVERWRITE, true);
|
||||
contentStream.drawImage(
|
||||
pdImage,
|
||||
0,
|
||||
0,
|
||||
pdPage.getMediaBox().getWidth(),
|
||||
pdPage.getMediaBox().getHeight());
|
||||
contentStream.close();
|
||||
}
|
||||
|
||||
// Save the modified PDF to a ByteArrayOutputStream
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
document.save(byteArrayOutputStream);
|
||||
document.close();
|
||||
|
||||
// Prepare the modified PDF for download
|
||||
ByteArrayInputStream inputStream =
|
||||
new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||
InputStreamResource resource = new InputStreamResource(inputStream);
|
||||
return resource;
|
||||
}
|
||||
|
||||
// Method to invert image colors
|
||||
private void invertImageColors(BufferedImage image) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
int rgba = image.getRGB(x, y);
|
||||
Color color = new Color(rgba, true);
|
||||
Color invertedColor =
|
||||
new Color(
|
||||
255 - color.getRed(),
|
||||
255 - color.getGreen(),
|
||||
255 - color.getBlue());
|
||||
image.setRGB(x, y, invertedColor.getRGB());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to convert BufferedImage to InputStream
|
||||
private File convertToBufferedImageTpFile(BufferedImage image) throws IOException {
|
||||
File file = new File("image.png");
|
||||
ImageIO.write(image, "png", file);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package stirling.software.SPDF.utils.misc;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.text.PDFTextStripperByArea;
|
||||
import org.apache.pdfbox.text.TextPosition;
|
||||
|
||||
public class PdfTextStripperCustom extends PDFTextStripperByArea {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @throws IOException If there is an error loading properties.
|
||||
*/
|
||||
public PdfTextStripperCustom() throws IOException {}
|
||||
|
||||
// To process the page text using stripper and returns the TextPosition and its values
|
||||
public List<List<TextPosition>> processPageCustom(PDPage page) throws IOException {
|
||||
|
||||
addRegion(
|
||||
"wholePage",
|
||||
new Rectangle2D.Float(
|
||||
page.getMediaBox().getLowerLeftX(),
|
||||
page.getMediaBox().getLowerLeftY(),
|
||||
page.getMediaBox().getWidth(),
|
||||
page.getMediaBox().getHeight()));
|
||||
extractRegions(page);
|
||||
|
||||
List<List<TextPosition>> textPositions = getCharactersByArticle();
|
||||
|
||||
return textPositions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package stirling.software.SPDF.utils.misc;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class ReplaceAndInvertColorStrategy extends PDFFile {
|
||||
|
||||
protected ReplaceAndInvert replaceAndInvert;
|
||||
|
||||
public ReplaceAndInvertColorStrategy(MultipartFile file, ReplaceAndInvert replaceAndInvert) {
|
||||
setFileInput(file);
|
||||
setReplaceAndInvert(replaceAndInvert);
|
||||
}
|
||||
|
||||
public abstract InputStreamResource replace() throws IOException;
|
||||
}
|
||||
@@ -27,9 +27,9 @@ server.servlet.context-path=${SYSTEM_ROOTURIPATH:/}
|
||||
|
||||
spring.devtools.restart.enabled=true
|
||||
spring.devtools.livereload.enabled=true
|
||||
|
||||
spring.thymeleaf.encoding=UTF-8
|
||||
|
||||
spring.web.resources.mime-mappings.webmanifest=application/manifest+json
|
||||
|
||||
spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000}
|
||||
#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/
|
||||
@@ -41,7 +41,7 @@ spring.datasource.username=sa
|
||||
spring.datasource.password=
|
||||
spring.h2.console.enabled=false
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
|
||||
server.servlet.session.timeout: 30m
|
||||
# Change the default URL path for OpenAPI JSON
|
||||
springdoc.api-docs.path=/v1/api-docs
|
||||
|
||||
@@ -49,3 +49,5 @@ springdoc.api-docs.path=/v1/api-docs
|
||||
springdoc.swagger-ui.url=/v1/api-docs
|
||||
|
||||
|
||||
posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq
|
||||
posthog.host=https://eu.i.posthog.com
|
||||
@@ -76,6 +76,9 @@ donate=تبرع
|
||||
color=لون
|
||||
sponsor=راعٍ
|
||||
info=معلومات
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=خط الأنابيب:
|
||||
pipelineOptions.saveButton=تنزيل
|
||||
pipelineOptions.validateButton=تحقق
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=تحويل من PDF
|
||||
navbar.sections.security=التوقيع والأمان
|
||||
navbar.sections.advance=متقدم
|
||||
navbar.sections.edit=عرض وتعديل
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=لم يتم العثور على الملف
|
||||
database.fileNullOrEmpty=يجب ألا يكون الملف فارغًا أو خاليًا
|
||||
database.failedImportFile=فشل استيراد الملف
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -380,7 +402,7 @@ home.scalePages.title=ضبط حجم/مقياس الصفحة
|
||||
home.scalePages.desc=تغيير حجم/مقياس الصفحة و/أو محتواها.
|
||||
scalePages.tags=تغيير الحجم,تعديل,الأبعاد,تكييف
|
||||
|
||||
home.pipeline.title=خط الأنابيب (متقدم)
|
||||
home.pipeline.title=خط الأنابيب
|
||||
home.pipeline.desc=تشغيل إجراءات متعددة على ملفات PDF عن طريق تحديد نصوص خط الأنابيب
|
||||
pipeline.tags=أتمتة,تسلسل,مبرمج,معالجة دفعات
|
||||
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=إزالة الصورة من PDF لتقليل حجم ا
|
||||
removeImagePdf.tags=إزالة الصورة,عمليات الصفحة,الخلفية,جانب الخادم
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=تم رفض الوصول
|
||||
login.oauth2InvalidTokenResponse=استجابة الرمز المميز غير صالحة
|
||||
login.oauth2InvalidIdToken=رمز الهوية غير صالح
|
||||
login.userIsDisabled=تم تعطيل المستخدم، تم حظر تسجيل الدخول حاليًا باستخدام اسم المستخدم هذا. يرجى الاتصال بالمسؤول.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=حجب تلقائي
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=تغيير
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF إلى PDF/A
|
||||
pdfToPDFA.header=PDF إلى PDF/A
|
||||
pdfToPDFA.credit=تستخدم هذه الخدمة OCRmyPDF لتحويل PDF/A.
|
||||
pdfToPDFA.credit=تستخدم هذه الخدمة ghostscript لتحويل PDF/A.
|
||||
pdfToPDFA.submit=تحويل
|
||||
pdfToPDFA.tip=لا يعمل حاليًا لمدخلات متعددة في وقت واحد
|
||||
pdfToPDFA.outputFormat=تنسيق الإخراج
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=الترخيص
|
||||
survey.nav=استطلاع
|
||||
survey.title=استطلاع Stirling-PDF
|
||||
survey.description=Stirling-PDF لا يحتوي على تتبع لذا نريد أن نسمع من مستخدمينا لتحسين Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=يرجى النظر في المشاركة في استطلاعنا!
|
||||
survey.disabled=(سيتم تعطيل النافذة المنبثقة للاستطلاع في التحديثات التالية ولكنها ستكون متاحة في أسفل الصفحة)
|
||||
survey.button=المشاركة في الاستطلاع
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=إزالة الصورة
|
||||
removeImage.header=إزالة الصورة
|
||||
removeImage.removeImage=إزالة الصورة
|
||||
removeImage.submit=إزالة الصورة
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Направете дарение
|
||||
color=Цвят
|
||||
sponsor=Спонсор
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Изтегли
|
||||
pipelineOptions.validateButton=Валидирай
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Преобразуване от PDF
|
||||
navbar.sections.security=Подписване и сигурност
|
||||
navbar.sections.advance=Разширено
|
||||
navbar.sections.edit=Преглед и редактиране
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Отказан достъп
|
||||
login.oauth2InvalidTokenResponse=Невалиден отговор на токена
|
||||
login.oauth2InvalidIdToken=Невалиден токен за идентификатор
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Автоматично редактиране
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Промени
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF към PDF/A
|
||||
pdfToPDFA.header=PDF към PDF/A
|
||||
pdfToPDFA.credit=Тази услуга използва OCRmyPDF за PDF/A преобразуване.
|
||||
pdfToPDFA.credit=Тази услуга използва ghostscript за PDF/A преобразуване.
|
||||
pdfToPDFA.submit=Преобразуване
|
||||
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
||||
pdfToPDFA.outputFormat=Изходен формат
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=Лиценз
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Download
|
||||
pipelineOptions.validateButton=Validate
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -380,7 +402,7 @@ home.scalePages.title=Adjust page size/scale
|
||||
home.scalePages.desc=Change the size/scale of page and/or its contents.
|
||||
scalePages.tags=resize,modify,dimension,adapt
|
||||
|
||||
home.pipeline.title=Pipeline (Advanced)
|
||||
home.pipeline.title=Pipeline
|
||||
home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts
|
||||
pipeline.tags=automate,sequence,scripted,batch-process
|
||||
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redact
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Canvia
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF a PDF/A
|
||||
pdfToPDFA.header=PDF a PDF/A
|
||||
pdfToPDFA.credit=Utilitza OCRmyPDF per la conversió a PDF/A
|
||||
pdfToPDFA.credit=Utilitza ghostscript per la conversió a PDF/A
|
||||
pdfToPDFA.submit=Converteix
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=License
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Přispějte
|
||||
color=Barva
|
||||
sponsor=Sponzor
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Stáhnout
|
||||
pipelineOptions.validateButton=Ověřit
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Převést z PDF
|
||||
navbar.sections.security=Podpis a Bezpečnost
|
||||
navbar.sections.advance=Pokročilé
|
||||
navbar.sections.edit=Prohlédnout a Upravit
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -380,7 +402,7 @@ home.scalePages.title=Upravit velikost/škálu stránky
|
||||
home.scalePages.desc=Změnit velikost/škálu stránky a/nebo její obsah.
|
||||
scalePages.tags=změnit velikost,upravit,rozměr,přizpůsobit
|
||||
|
||||
home.pipeline.title=Potrubí (Pokročilé)
|
||||
home.pipeline.title=Potrubí
|
||||
home.pipeline.desc=Spustit více akcí na PDF s definicí skriptů potrubí
|
||||
pipeline.tags=automatizovat,sekvence,skriptované,dávkové zpracování
|
||||
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redact
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Změnit
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF na PDF/A
|
||||
pdfToPDFA.header=PDF na PDF/A
|
||||
pdfToPDFA.credit=Tato služba používá OCRmyPDF pro konverzi do formátu PDF/A
|
||||
pdfToPDFA.credit=Tato služba používá ghostscript pro konverzi do formátu PDF/A
|
||||
pdfToPDFA.submit=Převést
|
||||
pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou
|
||||
pdfToPDFA.outputFormat=Výstupní formát
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=Licence
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Donér
|
||||
color=Farve
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Download
|
||||
pipelineOptions.validateButton=Validér
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Konvertér fra PDF
|
||||
navbar.sections.security=Signér & Sikkerhed
|
||||
navbar.sections.advance=Avanceret
|
||||
navbar.sections.edit=Vis & Redigér
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=Fil ikke fundet
|
||||
database.fileNullOrEmpty=Fil må ikke være null eller tom
|
||||
database.failedImportFile=Kunne ikke importere fil
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Fjern billede fra PDF for at reducere filstørrelse
|
||||
removeImagePdf.tags=Fjern Billede,Sideoperationer,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Adgang Nægtet
|
||||
login.oauth2InvalidTokenResponse=Ugyldigt Token Svar
|
||||
login.oauth2InvalidIdToken=Ugyldigt Id Token
|
||||
login.userIsDisabled=Bruger er deaktiveret, login er i øjeblikket blokeret med dette brugernavn. Kontakt venligst administratoren.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Rediger
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Ændre
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF Til PDF/A
|
||||
pdfToPDFA.header=PDF Til PDF/A
|
||||
pdfToPDFA.credit=Denne tjeneste bruger OCRmyPDF til PDF/A-konvertering
|
||||
pdfToPDFA.credit=Denne tjeneste bruger ghostscript til PDF/A-konvertering
|
||||
pdfToPDFA.submit=Konvertér
|
||||
pdfToPDFA.tip=Fungerer i øjeblikket ikke for flere input på én gang
|
||||
pdfToPDFA.outputFormat=Outputformat
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=License
|
||||
survey.nav=Undersøgelse
|
||||
survey.title=Stirling-PDF Undersøgelse
|
||||
survey.description=Stirling-PDF har ingen sporing, så vi vil gerne høre fra vores brugere for at forbedre Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Overvej venligst at deltage i vores undersøgelse!
|
||||
survey.disabled=(Undersøgelsespop-up vil blive deaktiveret i følgende opdateringer, men vil være tilgængelig i bunden af siden)
|
||||
survey.button=Tag Undersøgelsen
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Fjern billede
|
||||
removeImage.header=Fjern billede
|
||||
removeImage.removeImage=Fjern billede
|
||||
removeImage.submit=Fjern
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,11 +76,14 @@ donate=Spenden
|
||||
color=Farbe
|
||||
sponsor=Sponsor
|
||||
info=Informationen
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
legal.accessibility=Accessibility
|
||||
legal.cookie=Cookie Policy
|
||||
legal.privacy=Datenschutz
|
||||
legal.terms=AGB
|
||||
legal.accessibility=Barrierefreiheit
|
||||
legal.cookie=Cookie-Richtlinie
|
||||
legal.impressum=Impressum
|
||||
|
||||
###############
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Herunterladen
|
||||
pipelineOptions.validateButton=Validieren
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Konvertieren von PDF
|
||||
navbar.sections.security=Zeichen und Sicherheit
|
||||
navbar.sections.advance=Fortschrittlich
|
||||
navbar.sections.edit=Anzeigen und Bearbeiten
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=Datei nicht gefunden
|
||||
database.fileNullOrEmpty=Datei darf nicht null oder leer sein
|
||||
database.failedImportFile=Dateiimport fehlgeschlagen
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -380,7 +402,7 @@ home.scalePages.title=Seitengröße/Skalierung anpassen
|
||||
home.scalePages.desc=Größe/Skalierung der Seite und/oder des Inhalts ändern
|
||||
scalePages.tags=größe ändern,ändern,dimensionieren,anpassen
|
||||
|
||||
home.pipeline.title=Pipeline (Fortgeschritten)
|
||||
home.pipeline.title=Pipeline
|
||||
home.pipeline.desc=Mehrere Aktionen auf ein PDF anwenden, definiert durch ein Pipeline Skript
|
||||
pipeline.tags=automatisieren,sequenzieren,skriptgesteuert,batch prozess
|
||||
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Bild aus PDF entfernen, um die Dateigröße zu verringe
|
||||
removeImagePdf.tags=bild entfernen,seitenoperationen,back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Zugriff abgelehnt
|
||||
login.oauth2InvalidTokenResponse=Ungültige Token-Antwort
|
||||
login.oauth2InvalidIdToken=Ungültiges ID-Token
|
||||
login.userIsDisabled=Benutzer ist deaktiviert, die Anmeldung ist mit diesem Benutzernamen derzeit gesperrt. Bitte wenden Sie sich an den Administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatisch zensieren/schwärzen
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Ändern
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF zu PDF/A
|
||||
pdfToPDFA.header=PDF zu PDF/A
|
||||
pdfToPDFA.credit=Dieser Dienst verwendet OCRmyPDF für die PDF/A-Konvertierung
|
||||
pdfToPDFA.credit=Dieser Dienst verwendet ghostscript für die PDF/A-Konvertierung
|
||||
pdfToPDFA.submit=Konvertieren
|
||||
pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten.
|
||||
pdfToPDFA.outputFormat=Ausgabeformat
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=Lizenz
|
||||
survey.nav=Umfrage
|
||||
survey.title=Stirling-PDF-Umfrage
|
||||
survey.description=Stirling-PDF hat kein Tracking, daher möchten wir von unseren Benutzern hören, wie wir Stirling-PDF verbessern können!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Bitte nehmen Sie an unserer Umfrage teil!
|
||||
survey.disabled=(Das Umfrage-Popup wird in folgenden Updates deaktiviert, ist aber am Fuß der Seite verfügbar.)
|
||||
survey.button=Umfrage durchführen
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Bild entfernen
|
||||
removeImage.header=Bild entfernen
|
||||
removeImage.removeImage=Bild entfernen
|
||||
removeImage.submit=Bild entfernen
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Δωρισε
|
||||
color=Χρώμα
|
||||
sponsor=Yποστηρικτής
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Λήψη
|
||||
pipelineOptions.validateButton=Επικυρώνω
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Αυτόματο Μαύρισμα Κειμένου
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Αλλαγή
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF σε PDF/A
|
||||
pdfToPDFA.header=PDF σε PDF/A
|
||||
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί OCRmyPDF για PDF/A μετατροπή
|
||||
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί ghostscript για PDF/A μετατροπή
|
||||
pdfToPDFA.submit=Μετατροπή
|
||||
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=Άδεια
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Download
|
||||
pipelineOptions.validateButton=Validate
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed to import file
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -380,7 +402,7 @@ home.scalePages.title=Adjust page size/scale
|
||||
home.scalePages.desc=Change the size/scale of a page and/or its contents.
|
||||
scalePages.tags=resize,modify,dimension,adapt
|
||||
|
||||
home.pipeline.title=Pipeline (Advanced)
|
||||
home.pipeline.title=Pipeline
|
||||
home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts
|
||||
pipeline.tags=automate,sequence,scripted,batch-process
|
||||
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Advanced Colour options
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Advanced Colour options
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redact
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Change
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF To PDF/A
|
||||
pdfToPDFA.header=PDF To PDF/A
|
||||
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
||||
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion
|
||||
pdfToPDFA.submit=Convert
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
@@ -1132,7 +1182,9 @@ licenses.license=Licence
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey to have input on the future of Stirling-PDF!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
survey.dontShowAgain=Don't show again
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Download
|
||||
pipelineOptions.validateButton=Validate
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redact
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Change
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF To PDF/A
|
||||
pdfToPDFA.header=PDF To PDF/A
|
||||
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
||||
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion
|
||||
pdfToPDFA.submit=Convert
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=License
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Donar
|
||||
color=Color
|
||||
sponsor=Patrocinador
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Canalización:
|
||||
pipelineOptions.saveButton=Descargar
|
||||
pipelineOptions.validateButton=Validar
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Convertir desde PDF
|
||||
navbar.sections.security=Señalización y seguridad
|
||||
navbar.sections.advance=Avanzado
|
||||
navbar.sections.edit=Ver y Editar
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=Archivo no encontrado
|
||||
database.fileNullOrEmpty=El archivo no debe ser nulo o vacío.
|
||||
database.failedImportFile=Archivo de importación fallido
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -380,7 +402,7 @@ home.scalePages.title=Escalar/ajustar tamaño de página
|
||||
home.scalePages.desc=Escalar/cambiar el tamaño de una pagina y/o su contenido
|
||||
scalePages.tags=cambiar tamaño,modificar,dimensionar,adaptar
|
||||
|
||||
home.pipeline.title=Secuencia (Avanzado)
|
||||
home.pipeline.title=Secuencia
|
||||
home.pipeline.desc=Ejecutar varias tareas a PDFs definiendo una secuencia de comandos
|
||||
pipeline.tags=automatizar,secuencia,con script,proceso por lotes
|
||||
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Eliminar imagen del PDF> para reducir el tamaño de arc
|
||||
removeImagePdf.tags=Eliminar imagen,Operaciones de página,Back end,lado del servidor
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Acceso denegado
|
||||
login.oauth2InvalidTokenResponse=Respuesta de token no válida
|
||||
login.oauth2InvalidIdToken=Token de identificación no válido
|
||||
login.userIsDisabled=El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, póngase en contacto con el administrador.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redactar
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Cambiar
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF a PDF/A
|
||||
pdfToPDFA.header=PDF a PDF/A
|
||||
pdfToPDFA.credit=Este servicio usa OCRmyPDF para la conversión a PDF/A
|
||||
pdfToPDFA.credit=Este servicio usa ghostscript para la conversión a PDF/A
|
||||
pdfToPDFA.submit=Convertir
|
||||
pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez
|
||||
pdfToPDFA.outputFormat=Formato de salida
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=Licencia
|
||||
survey.nav=Encuesta
|
||||
survey.title=Encuesta Stirling-PDF
|
||||
survey.description=Stirling-PDF no tiene seguimiento, por lo que queremos escuchar a nuestros usuarios para mejorar Stirling-PDF.
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=¡Considere realizar nuestra encuesta!
|
||||
survey.disabled=(La ventana emergente de la encuesta se desactivará en las siguientes actualizaciones, pero estará disponible al pie de la página.)
|
||||
survey.button=Realizar encuesta
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Eliminar imagen
|
||||
removeImage.header=Eliminar imagen
|
||||
removeImage.removeImage=Eliminar imagen
|
||||
removeImage.submit=Eliminar imagen
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Download
|
||||
pipelineOptions.validateButton=Validate
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF
|
||||
navbar.sections.security=Sign & Security
|
||||
navbar.sections.advance=Advanced
|
||||
navbar.sections.edit=View & Edit
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Idatzi
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Aldatu
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDFa PDF/A bihurtu
|
||||
pdfToPDFA.header=PDFa PDF/A bihurtu
|
||||
pdfToPDFA.credit=Zerbitzu honek OCRmyPDF erabiltzen du PDFak PDF/A bihurtzeko
|
||||
pdfToPDFA.credit=Zerbitzu honek ghostscript erabiltzen du PDFak PDF/A bihurtzeko
|
||||
pdfToPDFA.submit=Bihurtu
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=License
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Faire un don
|
||||
color=Couleur
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Télécharger
|
||||
pipelineOptions.validateButton=Valider
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Convertir depuis PDF
|
||||
navbar.sections.security=Signature et sécurité
|
||||
navbar.sections.advance=Mode avancé
|
||||
navbar.sections.edit=Voir et modifier
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -380,7 +402,7 @@ home.scalePages.title=Ajuster l’échelle ou la taille
|
||||
home.scalePages.desc=Modifiez la taille ou l’échelle d’une page et/ou de son contenu.
|
||||
scalePages.tags=ajuster,redimensionner,resize,modify,dimension,adapt
|
||||
|
||||
home.pipeline.title=Pipeline (avancé)
|
||||
home.pipeline.title=Pipeline
|
||||
home.pipeline.desc=Exécutez plusieurs actions sur les PDF en définissant des scripts de pipeline.
|
||||
pipeline.tags=automatiser,séquencer,automate,sequence,scripted,batch-process
|
||||
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Accès refusé
|
||||
login.oauth2InvalidTokenResponse=Réponse contenant le jeton est invalide
|
||||
login.oauth2InvalidIdToken=Jeton d'identification invalide
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Caviarder automatiquement
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Modifier
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF en PDF/A
|
||||
pdfToPDFA.header=PDF en PDF/A
|
||||
pdfToPDFA.credit=Ce service utilise OCRmyPDF pour la conversion en PDF/A.
|
||||
pdfToPDFA.credit=Ce service utilise ghostscript pour la conversion en PDF/A.
|
||||
pdfToPDFA.submit=Convertir
|
||||
pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois
|
||||
pdfToPDFA.outputFormat=Format de sortie
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=Licence
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Síntiúis
|
||||
color=Dath
|
||||
sponsor=Urraitheoir
|
||||
info=Eolas
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Píblíne:
|
||||
pipelineOptions.saveButton=Íosluchtaigh
|
||||
pipelineOptions.validateButton=Bailíochtaigh
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Tiontaigh ó PDF
|
||||
navbar.sections.security=Comhartha & Slándáil
|
||||
navbar.sections.advance=Casta
|
||||
navbar.sections.edit=Féach ar & Cuir in Eagar
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=Comhad gan aimsiú
|
||||
database.fileNullOrEmpty=Níor cheart go mbeadh an comhad ar neamhní nó folamh
|
||||
database.failedImportFile=Theip ar iompórtáil an chomhaid
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Rochtain Diúltaithe
|
||||
login.oauth2InvalidTokenResponse=Freagra Comhartha Neamhbhailí
|
||||
login.oauth2InvalidIdToken=Comhartha Aitheantais Neamhbhailí
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Auto Redact
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Athrú
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF Go PDF/A
|
||||
pdfToPDFA.header=PDF Go PDF/A
|
||||
pdfToPDFA.credit=Úsáideann an tseirbhís seo OCRmyPDF chun PDF/A a thiontú
|
||||
pdfToPDFA.credit=Úsáideann an tseirbhís seo ghostscript chun PDF/A a thiontú
|
||||
pdfToPDFA.submit=Tiontaigh
|
||||
pdfToPDFA.tip=Faoi láthair ní oibríonn sé le haghaidh ionchuir iolracha ag an am céanna
|
||||
pdfToPDFA.outputFormat=Formáid aschuir
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=Ceadúnas
|
||||
survey.nav=Suirbhé
|
||||
survey.title=Suirbhé Stirling-PDF
|
||||
survey.description=Níl aon rian ar Stirling-PDF agus mar sin ba mhaith linn cloisteáil ónár n-úsáideoirí chun feabhas a chur ar Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Smaoinigh ar ár suirbhé a dhéanamh le do thoil!
|
||||
survey.disabled=(Díchumasófar aníos an tsuirbhé sna nuashonruithe seo a leanas ach beidh siad ar fáil ag bun an leathanaigh)
|
||||
survey.button=Tóg Suirbhé
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=पृष्ठ
|
||||
pages=पृष्ठों
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Download
|
||||
pipelineOptions.validateButton=Validate
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=पीडीएफ से कनवर्ट कर
|
||||
navbar.sections.security=संकेत और सुरक्षा
|
||||
navbar.sections.advance=उन्नत
|
||||
navbar.sections.edit=देखें और संपादित करें
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Access Denied
|
||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||
login.oauth2InvalidIdToken=Invalid Id Token
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=स्वत: गोपनीयकरण
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=बदलें
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF से PDF/A में
|
||||
pdfToPDFA.header=PDF से PDF/A में
|
||||
pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए OCRmyPDF का उपयोग किया जाता है।
|
||||
pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए ghostscript का उपयोग किया जाता है।
|
||||
pdfToPDFA.submit=परिवर्तित करें
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=License
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ donate=Doniraj
|
||||
color=Boja
|
||||
sponsor=Sponzor
|
||||
info=Info
|
||||
pro=Pro
|
||||
page=Page
|
||||
pages=Pages
|
||||
|
||||
legal.privacy=Privacy Policy
|
||||
legal.terms=Terms and Conditions
|
||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Preuzmi datoteku
|
||||
pipelineOptions.validateButton=Potvrdi
|
||||
|
||||
########################
|
||||
# ENTERPRISE EDITION #
|
||||
########################
|
||||
enterpriseEdition.button=Upgrade to Pro
|
||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||
|
||||
|
||||
#################
|
||||
# Analytics #
|
||||
#################
|
||||
analytics.title=Do you want make Stirling PDF better?
|
||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||
analytics.enable=Enable analytics
|
||||
analytics.disable=Disable analytics
|
||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||
|
||||
#############
|
||||
# NAVBAR #
|
||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Pretvori iz PDF
|
||||
navbar.sections.security=Potpis & sigurnost
|
||||
navbar.sections.advance=Napredno
|
||||
navbar.sections.edit=Pregled & Uređivanje
|
||||
navbar.sections.popular=Popular
|
||||
|
||||
#############
|
||||
# SETTINGS #
|
||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not Found
|
||||
database.fileNullOrEmpty=File must not be null or empty
|
||||
database.failedImportFile=Failed Import File
|
||||
|
||||
session.expired=Your session has expired. Please refresh the page and try again.
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
@@ -380,7 +402,7 @@ home.scalePages.title=Prilagodite veličinu/razmjer stranice
|
||||
home.scalePages.desc=Promijenite veličinu/razmjer stranice i/ili njezin sadržaj.
|
||||
scalePages.tags=izmjena,modifikacija,dimenzija,adaptacija
|
||||
|
||||
home.pipeline.title=Pipeline (Advanced)
|
||||
home.pipeline.title=Pipeline
|
||||
home.pipeline.desc=Izvršite više radnji na PDF-ovima definiranjem skripti u pipeline-u
|
||||
pipeline.tags=automatizacija,sekvenciranje,skriptirano,batch-process
|
||||
|
||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||
|
||||
|
||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||
|
||||
#replace-invert-color
|
||||
replace-color.title=Replace-Invert-Color
|
||||
replace-color.header=Replace-Invert Color PDF
|
||||
home.replaceColorPdf.title=Replace and Invert Color
|
||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||
replace-color.selectText.1=Replace or Invert color Options
|
||||
replace-color.selectText.2=Default(Default high contrast colors)
|
||||
replace-color.selectText.3=Custom(Customized colors)
|
||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||
replace-color.selectText.5=High contrast color options
|
||||
replace-color.selectText.6=white text on black background
|
||||
replace-color.selectText.7=Black text on white background
|
||||
replace-color.selectText.8=Yellow text on black background
|
||||
replace-color.selectText.9=Green text on black background
|
||||
replace-color.selectText.10=Choose text Color
|
||||
replace-color.selectText.11=Choose background Color
|
||||
replace-color.submit=Replace
|
||||
|
||||
|
||||
|
||||
###########################
|
||||
# #
|
||||
# WEB PAGES #
|
||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Pristup odbijen
|
||||
login.oauth2InvalidTokenResponse=Nevažeći odgovor tokena
|
||||
login.oauth2InvalidIdToken=Nevažeći ID token
|
||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||
|
||||
login.alreadyLoggedIn=You are already logged in to
|
||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||
login.toManySessions=You have too many active sessions
|
||||
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Automatsko uređivanje
|
||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Promijeniti
|
||||
#pdfToPDFA
|
||||
pdfToPDFA.title=PDF u PDF/A
|
||||
pdfToPDFA.header=PDF u PDF/A
|
||||
pdfToPDFA.credit=Ova usluga koristi OCRmyPDF za PDF/A pretvorbu
|
||||
pdfToPDFA.credit=Ova usluga koristi ghostscript za PDF/A pretvorbu
|
||||
pdfToPDFA.submit=Pretvoriti
|
||||
pdfToPDFA.tip=Trenutno ne radi za više unosa odjednom
|
||||
pdfToPDFA.outputFormat=Izlazni format
|
||||
@@ -1132,6 +1182,8 @@ licenses.license=Licenca
|
||||
survey.nav=Survey
|
||||
survey.title=Stirling-PDF Survey
|
||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||
survey.changes2=With these changes we are getting paid business support and funding
|
||||
survey.please=Please consider taking our survey!
|
||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||
survey.button=Take Survey
|
||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
||||
removeImage.header=Remove image
|
||||
removeImage.removeImage=Remove image
|
||||
removeImage.submit=Remove image
|
||||
|
||||
|
||||
splitByChapters.title=Split PDF by Chapters
|
||||
splitByChapters.header=Split PDF by Chapters
|
||||
splitByChapters.bookmarkLevel=Bookmark Level
|
||||
splitByChapters.includeMetadata=Include Metadata
|
||||
splitByChapters.allowDuplicates=Allow Duplicates
|
||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||
splitByChapters.submit=Split PDF
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user