Compare commits
109 Commits
Frooodle/f
...
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 | ||
|
|
d389b5e2f3 | ||
|
|
de97492f39 | ||
|
|
9661e94092 | ||
|
|
2cfb553320 | ||
|
|
909a3347a0 | ||
|
|
de4144a1a4 | ||
|
|
bb1c859e0d | ||
|
|
b2862a3fc4 | ||
|
|
8788a7ee34 | ||
|
|
8c01425eee | ||
|
|
4d47e535b5 | ||
|
|
1fb78c3124 | ||
|
|
6a9dd4ea95 | ||
|
|
0773b8e11b | ||
|
|
184d89c44a | ||
|
|
df2e9bfc6e | ||
|
|
0045fe852b | ||
|
|
7d46d61d9e | ||
|
|
f8404ce9e9 | ||
|
|
7fad973a77 | ||
|
|
6410a99cf3 | ||
|
|
62e7c7e073 | ||
|
|
1d29a500b3 | ||
|
|
08b085c763 | ||
|
|
f256e8f029 | ||
|
|
0ad8c635ad | ||
|
|
12ff0ecac2 | ||
|
|
291bad4a2a | ||
|
|
c6ee96512a | ||
|
|
db563c765d | ||
|
|
6f52189ed2 | ||
|
|
580313151b | ||
|
|
765289c89e | ||
|
|
3d8686211d | ||
|
|
0a98e3bde3 | ||
|
|
78211d09c5 | ||
|
|
82219dd899 | ||
|
|
3c04486348 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -110,7 +110,6 @@ watchedFolders/
|
|||||||
*.war
|
*.war
|
||||||
*.nar
|
*.nar
|
||||||
*.ear
|
*.ear
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.rar
|
*.rar
|
||||||
*.db
|
*.db
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.20.2
|
FROM alpine:3.20.3
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts /scripts
|
COPY scripts /scripts
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ RUN DOCKER_ENABLE_SECURITY=true \
|
|||||||
./gradlew clean build
|
./gradlew clean build
|
||||||
|
|
||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:3.20.2
|
FROM alpine:3.20.3
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts /scripts
|
COPY scripts /scripts
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# use alpine
|
# use alpine
|
||||||
FROM alpine:3.20.2
|
FROM alpine:3.20.3
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
|||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
||||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||||
|
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||||
COPY pipeline /pipeline
|
COPY pipeline /pipeline
|
||||||
COPY build/libs/*.jar app.jar
|
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 \
|
su-exec \
|
||||||
openjdk21-jre && \
|
openjdk21-jre && \
|
||||||
# User permissions
|
# User permissions
|
||||||
mkdir /configs /logs /customFiles && \
|
mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto && \
|
||||||
chmod +x /scripts/*.sh && \
|
chmod +x /scripts/*.sh && \
|
||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
||||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||||
|
|||||||
@@ -257,9 +257,11 @@ To override the default configuration, you can add the following to `/.git/Stirl
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
server:
|
server:
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0 # Not working - use instead address
|
||||||
|
address: 0.0.0.0
|
||||||
port: 3000
|
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.
|
**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.
|
||||||
|
|
||||||
|
|||||||
64
README.md
64
README.md
@@ -100,6 +100,8 @@ Demo of the app is available [here](https://stirlingpdf.io).
|
|||||||
- [PDF-LIB.js](https://github.com/Hopding/pdf-lib)
|
- [PDF-LIB.js](https://github.com/Hopding/pdf-lib)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
### Windows
|
||||||
|
For windows users download the latest Stirling-PDF.exe from our [release](https://github.com/Stirling-Tools/Stirling-PDF/releases) section or by clicking [here](https://github.com/Stirling-Tools/Stirling-PDF/releases/latest/download/Stirling-PDF.exe)
|
||||||
|
|
||||||
### Locally
|
### Locally
|
||||||
|
|
||||||
@@ -170,42 +172,42 @@ Stirling PDF currently supports 38!
|
|||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| ------------------------------------------- | -------------------------------------- |
|
| ------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
|
|
||||||
## Contributing (creating issues, translations, fixing bugs, etc.)
|
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||||
|
|
||||||
|
|||||||
29
build.gradle
29
build.gradle
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
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 "io.spring.dependency-management" version "1.1.6"
|
||||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||||
id "io.swagger.swaggerhub" version "1.3.2"
|
id "io.swagger.swaggerhub" version "1.3.2"
|
||||||
@@ -13,7 +13,7 @@ plugins {
|
|||||||
import com.github.jk1.license.render.*
|
import com.github.jk1.license.render.*
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
springBootVersion = "3.3.3"
|
springBootVersion = "3.3.4"
|
||||||
pdfboxVersion = "3.0.3"
|
pdfboxVersion = "3.0.3"
|
||||||
logbackVersion = "1.5.7"
|
logbackVersion = "1.5.7"
|
||||||
imageioVersion = "3.11.0"
|
imageioVersion = "3.11.0"
|
||||||
@@ -22,7 +22,7 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.28.3"
|
version = "0.30.0"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
// 17 is lowest but we support and recommend 21
|
// 17 is lowest but we support and recommend 21
|
||||||
@@ -32,6 +32,12 @@ java {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
|
maven {
|
||||||
|
url "https://build.shibboleth.net/nexus/content/repositories/releases/"
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url "https://build.shibboleth.net/maven/releases/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseReport {
|
licenseReport {
|
||||||
@@ -115,7 +121,7 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
//security updates
|
//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")
|
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-jetty:$springBootVersion"
|
||||||
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$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") {
|
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
||||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
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-data-jpa:$springBootVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$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
|
//2.2.x requires rebuild of DB file.. need migration path
|
||||||
runtimeOnly "com.h2database:h2:2.1.214"
|
runtimeOnly "com.h2database:h2:2.1.214"
|
||||||
// implementation "com.h2database:h2:2.2.224"
|
// implementation "com.h2database:h2:2.2.224"
|
||||||
@@ -162,7 +173,7 @@ dependencies {
|
|||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$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"
|
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
||||||
//general PDF
|
//general PDF
|
||||||
|
|
||||||
@@ -184,14 +195,14 @@ dependencies {
|
|||||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||||
implementation "io.micrometer:micrometer-core:1.13.3"
|
implementation "io.micrometer:micrometer-core:1.13.4"
|
||||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||||
implementation "org.commonmark:commonmark:0.22.0"
|
implementation "org.commonmark:commonmark:0.23.0"
|
||||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0"
|
implementation "org.commonmark:commonmark-ext-gfm-tables:0.23.0"
|
||||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||||
implementation "com.fathzer:javaluator:3.0.4"
|
implementation "com.fathzer:javaluator:3.0.5"
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
|
||||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
appVersion: 0.28.3
|
appVersion: 0.29.0
|
||||||
description: locally hosted web application that allows you to perform various operations
|
description: locally hosted web application that allows you to perform various operations
|
||||||
on PDF files
|
on PDF files
|
||||||
home: https://github.com/Stirling-Tools/Stirling-PDF
|
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||||
|
|||||||
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 |
|
| odt | .odt |
|
||||||
| doc | .doc |
|
| doc | .doc |
|
||||||
|
|
||||||
@ocr
|
@ocr @pdfa1
|
||||||
Scenario: PDFA
|
Scenario: PDFA
|
||||||
Given I use an example file at "exampleFiles/pdfa2.pdf" as parameter "fileInput"
|
Given I use an example file at "exampleFiles/pdfa2.pdf" as parameter "fileInput"
|
||||||
And the request data includes
|
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 extension ".pdf"
|
||||||
And the response file should have size greater than 100
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
@ocr
|
@ocr @pdfa2
|
||||||
Scenario: PDFA1
|
Scenario: PDFA1
|
||||||
Given I use an example file at "exampleFiles/pdfa1.pdf" as parameter "fileInput"
|
Given I use an example file at "exampleFiles/pdfa1.pdf" as parameter "fileInput"
|
||||||
And the request data includes
|
And the request data includes
|
||||||
@@ -218,6 +218,28 @@ Feature: API Validation
|
|||||||
| .odt |
|
| .odt |
|
||||||
| .pptx |
|
| .pptx |
|
||||||
| .rtf |
|
| .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"
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Security-Fat
|
container_name: Stirling-PDF-Security-Fat
|
||||||
@@ -8,7 +7,7 @@ services:
|
|||||||
limits:
|
limits:
|
||||||
memory: 4G
|
memory: 4G
|
||||||
healthcheck:
|
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
|
interval: 5s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 16
|
retries: 16
|
||||||
@@ -20,7 +19,7 @@ services:
|
|||||||
- /stirling/latest/logs:/logs:rw
|
- /stirling/latest/logs:/logs:rw
|
||||||
environment:
|
environment:
|
||||||
DOCKER_ENABLE_SECURITY: "true"
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
SECURITY_ENABLELOGIN: "true"
|
SECURITY_ENABLELOGIN: "false"
|
||||||
PUID: 1002
|
PUID: 1002
|
||||||
PGID: 1002
|
PGID: 1002
|
||||||
UMASK: "022"
|
UMASK: "022"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Security
|
container_name: Stirling-PDF-Security
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Security
|
container_name: Stirling-PDF-Security
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Ultra-Lite-Security
|
container_name: Stirling-PDF-Ultra-Lite-Security
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF-Ultra-Lite
|
container_name: Stirling-PDF-Ultra-Lite
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3.3'
|
|
||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
container_name: Stirling-PDF
|
container_name: Stirling-PDF
|
||||||
|
|||||||
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
|
||||||
26
src/main/java/stirling/software/SPDF/EE/EEAppConfig.java
Normal file
26
src/main/java/stirling/software/SPDF/EE/EEAppConfig.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package stirling.software.SPDF.EE;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Lazy
|
||||||
|
public class EEAppConfig {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(EEAppConfig.class);
|
||||||
|
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Autowired private LicenseKeyChecker licenseKeyChecker;
|
||||||
|
|
||||||
|
@Bean(name = "runningEE")
|
||||||
|
public boolean runningEnterpriseEdition() {
|
||||||
|
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 io.github.pixee.security.SystemCommand;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class LibreOfficeListener {
|
public class LibreOfficeListener {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class);
|
private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class);
|
||||||
@@ -31,7 +34,7 @@ public class LibreOfficeListener {
|
|||||||
private LibreOfficeListener() {}
|
private LibreOfficeListener() {}
|
||||||
|
|
||||||
private boolean isListenerRunning() {
|
private boolean isListenerRunning() {
|
||||||
System.out.println("waiting for listener to start");
|
log.info("waiting for listener to start");
|
||||||
try (Socket socket = new Socket()) {
|
try (Socket socket = new Socket()) {
|
||||||
socket.connect(
|
socket.connect(
|
||||||
new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
|
new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@@ -30,15 +31,36 @@ public class SPdfApplication {
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
||||||
|
|
||||||
@Autowired private Environment env;
|
@Autowired private Environment env;
|
||||||
|
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private static String serverPortStatic;
|
private static String serverPortStatic;
|
||||||
|
|
||||||
@Value("${server.port:8080}")
|
@Value("${server.port:8080}")
|
||||||
public void setServerPortStatic(String port) {
|
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
|
@PostConstruct
|
||||||
@@ -48,13 +70,17 @@ public class SPdfApplication {
|
|||||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||||
if (browserOpen) {
|
if (browserOpen) {
|
||||||
try {
|
try {
|
||||||
String url = "http://localhost:" + getNonStaticPort();
|
String url = "http://localhost:" + getStaticPort();
|
||||||
|
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
Runtime rt = Runtime.getRuntime();
|
Runtime rt = Runtime.getRuntime();
|
||||||
if (os.contains("win")) {
|
if (os.contains("win")) {
|
||||||
// For Windows
|
// For Windows
|
||||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
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) {
|
} catch (Exception e) {
|
||||||
logger.error("Error opening browser: {}", e.getMessage());
|
logger.error("Error opening browser: {}", e.getMessage());
|
||||||
@@ -70,15 +96,13 @@ public class SPdfApplication {
|
|||||||
app.addInitializers(new ConfigInitializer());
|
app.addInitializers(new ConfigInitializer());
|
||||||
Map<String, String> propertyFiles = new HashMap<>();
|
Map<String, String> propertyFiles = new HashMap<>();
|
||||||
|
|
||||||
// stirling pdf settings file
|
// External config files
|
||||||
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
||||||
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
||||||
} else {
|
} else {
|
||||||
logger.warn(
|
logger.warn("External configuration file 'configs/settings.yml' does not exist.");
|
||||||
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom javs settings file
|
|
||||||
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
||||||
String existingLocation =
|
String existingLocation =
|
||||||
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||||
@@ -101,19 +125,14 @@ public class SPdfApplication {
|
|||||||
|
|
||||||
app.run(args);
|
app.run(args);
|
||||||
|
|
||||||
try {
|
// Ensure directories are created
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new RuntimeException("Thread interrupted while sleeping", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(Path.of("customFiles/static/"));
|
Files.createDirectories(Path.of("customFiles/static/"));
|
||||||
Files.createDirectories(Path.of("customFiles/templates/"));
|
Files.createDirectories(Path.of("customFiles/templates/"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error creating directories: {}", e.getMessage());
|
logger.error("Error creating directories: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
printStartupLogs();
|
printStartupLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,4 +135,52 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean(name = "termsAndConditions")
|
||||||
|
public String termsAndConditions() {
|
||||||
|
return applicationProperties.getLegal().getTermsAndConditions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "privacyPolicy")
|
||||||
|
public String privacyPolicy() {
|
||||||
|
return applicationProperties.getLegal().getPrivacyPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "cookiePolicy")
|
||||||
|
public String cookiePolicy() {
|
||||||
|
return applicationProperties.getLegal().getCookiePolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "impressum")
|
||||||
|
public String impressum() {
|
||||||
|
return applicationProperties.getLegal().getImpressum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "accessibilityStatement")
|
||||||
|
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.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -18,7 +19,7 @@ class AppUpdateService {
|
|||||||
@Bean(name = "shouldShow")
|
@Bean(name = "shouldShow")
|
||||||
@Scope("request")
|
@Scope("request")
|
||||||
public boolean shouldShow() {
|
public boolean shouldShow() {
|
||||||
boolean showUpdate = applicationProperties.getSystem().getShowUpdate();
|
boolean showUpdate = applicationProperties.getSystem().isShowUpdate();
|
||||||
boolean showAdminResult = (showAdmin != null) ? showAdmin.getShowUpdateOnlyAdmins() : true;
|
boolean showAdminResult = (showAdmin != null) ? showAdmin.getShowUpdateOnlyAdmins() : true;
|
||||||
return showUpdate && showAdminResult;
|
return showUpdate && showAdminResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class Beans implements WebMvcConfigurer {
|
public class LocaleConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@@ -13,6 +13,8 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MetricsFilter extends OncePerRequestFilter {
|
public class MetricsFilter extends OncePerRequestFilter {
|
||||||
@@ -30,32 +32,17 @@ public class MetricsFilter extends OncePerRequestFilter {
|
|||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
|
|
||||||
// System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) {
|
||||||
// Ignore static resources
|
HttpSession session = request.getSession(false);
|
||||||
if (!(uri.startsWith("/js")
|
String sessionId = (session != null) ? session.getId() : "no-session";
|
||||||
|| uri.startsWith("/v1/api-docs")
|
|
||||||
|| uri.endsWith("robots.txt")
|
|
||||||
|| uri.startsWith("/images")
|
|
||||||
|| uri.endsWith(".png")
|
|
||||||
|| uri.endsWith(".ico")
|
|
||||||
|| uri.endsWith(".css")
|
|
||||||
|| uri.endsWith(".map")
|
|
||||||
|| uri.endsWith(".svg")
|
|
||||||
|| uri.endsWith(".js")
|
|
||||||
|| uri.contains("swagger")
|
|
||||||
|| uri.startsWith("/api/v1/info")
|
|
||||||
|| uri.startsWith("/site.webmanifest")
|
|
||||||
|| uri.startsWith("/fonts")
|
|
||||||
|| uri.startsWith("/pdfjs"))) {
|
|
||||||
|
|
||||||
Counter counter =
|
Counter counter =
|
||||||
Counter.builder("http.requests")
|
Counter.builder("http.requests")
|
||||||
.tag("uri", uri)
|
.tag("session", sessionId)
|
||||||
.tag("method", request.getMethod())
|
.tag("method", request.getMethod())
|
||||||
|
.tag("uri", uri)
|
||||||
.register(meterRegistry);
|
.register(meterRegistry);
|
||||||
|
|
||||||
counter.increment();
|
counter.increment();
|
||||||
// System.out.println("Counted");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
|
|||||||
@@ -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.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config.interfaces;
|
||||||
|
|
||||||
public interface ShowAdminInterface {
|
public interface ShowAdminInterface {
|
||||||
default boolean getShowUpdateOnlyAdmins() {
|
default boolean getShowUpdateOnlyAdmins() {
|
||||||
@@ -7,7 +7,7 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
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.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
@@ -20,7 +20,7 @@ class AppUpdateAuthService implements ShowAdminInterface {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean getShowUpdateOnlyAdmins() {
|
public boolean getShowUpdateOnlyAdmins() {
|
||||||
boolean showUpdate = applicationProperties.getSystem().getShowUpdate();
|
boolean showUpdate = applicationProperties.getSystem().isShowUpdate();
|
||||||
if (!showUpdate) {
|
if (!showUpdate) {
|
||||||
return showUpdate;
|
return showUpdate;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -14,9 +16,12 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class FirstLoginFilter extends OncePerRequestFilter {
|
public class FirstLoginFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
@@ -50,6 +55,22 @@ public class FirstLoginFilter extends OncePerRequestFilter {
|
|||||||
return;
|
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);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.UUID;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
|
|
||||||
@@ -39,15 +34,6 @@ public class InitialSecuritySetup {
|
|||||||
initializeInternalApiUser();
|
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 {
|
private void initializeAdminUser() throws IOException {
|
||||||
String initialUsername =
|
String initialUsername =
|
||||||
applicationProperties.getSecurity().getInitialLogin().getUsername();
|
applicationProperties.getSecurity().getInitialLogin().getUsername();
|
||||||
@@ -89,33 +75,4 @@ public class InitialSecuritySetup {
|
|||||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
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;
|
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.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.security.authentication.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.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
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.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
||||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
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.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
|
||||||
import stirling.software.SPDF.model.User;
|
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
|
@Slf4j
|
||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
@Autowired private CustomUserDetailsService userDetailsService;
|
@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
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
@@ -73,13 +71,18 @@ public class SecurityConfiguration {
|
|||||||
@Autowired private FirstLoginFilter firstLoginFilter;
|
@Autowired private FirstLoginFilter firstLoginFilter;
|
||||||
@Autowired private SessionPersistentRegistry sessionRegistry;
|
@Autowired private SessionPersistentRegistry sessionRegistry;
|
||||||
|
|
||||||
|
@Autowired private ConvertResponseToAuthentication convertResponseToAuthentication;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
http.authenticationManager(authenticationManager(http));
|
||||||
|
|
||||||
if (loginEnabledValue) {
|
if (loginEnabledValue) {
|
||||||
|
http.addFilterBefore(
|
||||||
http.csrf(csrf -> csrf.disable());
|
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||||
|
http.csrf(csrf -> csrf.disable());
|
||||||
|
}
|
||||||
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
http.sessionManagement(
|
http.sessionManagement(
|
||||||
@@ -135,6 +138,7 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
return trimmedUri.startsWith("/login")
|
return trimmedUri.startsWith("/login")
|
||||||
|| trimmedUri.startsWith("/oauth")
|
|| trimmedUri.startsWith("/oauth")
|
||||||
|
|| trimmedUri.startsWith("/saml2")
|
||||||
|| trimmedUri.endsWith(".svg")
|
|| trimmedUri.endsWith(".svg")
|
||||||
|| trimmedUri.startsWith(
|
|| trimmedUri.startsWith(
|
||||||
"/register")
|
"/register")
|
||||||
@@ -152,8 +156,8 @@ public class SecurityConfiguration {
|
|||||||
.authenticated());
|
.authenticated());
|
||||||
|
|
||||||
// Handle OAUTH2 Logins
|
// Handle OAUTH2 Logins
|
||||||
if (applicationProperties.getSecurity().getOAUTH2() != null
|
if (applicationProperties.getSecurity().getOauth2() != null
|
||||||
&& applicationProperties.getSecurity().getOAUTH2().getEnabled()
|
&& applicationProperties.getSecurity().getOauth2().getEnabled()
|
||||||
&& !applicationProperties
|
&& !applicationProperties
|
||||||
.getSecurity()
|
.getSecurity()
|
||||||
.getLoginMethod()
|
.getLoginMethod()
|
||||||
@@ -184,191 +188,82 @@ public class SecurityConfiguration {
|
|||||||
userService,
|
userService,
|
||||||
loginAttemptService))
|
loginAttemptService))
|
||||||
.userAuthoritiesMapper(
|
.userAuthoritiesMapper(
|
||||||
userAuthoritiesMapper())))
|
userAuthoritiesMapper)))
|
||||||
.logout(
|
.logout(
|
||||||
logout ->
|
logout ->
|
||||||
logout.logoutSuccessHandler(
|
logout.logoutSuccessHandler(
|
||||||
new CustomOAuth2LogoutSuccessHandler(
|
new CustomOAuth2LogoutSuccessHandler(
|
||||||
applicationProperties)));
|
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 {
|
} else {
|
||||||
http.csrf(csrf -> csrf.disable())
|
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.csrf(csrf -> csrf.disable());
|
||||||
|
}
|
||||||
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client Registration Repository for OAUTH2 OIDC Login
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(
|
||||||
value = "security.oauth2.enabled",
|
name = "security.saml.enabled",
|
||||||
havingValue = "true",
|
havingValue = "true",
|
||||||
matchIfMissing = false)
|
matchIfMissing = false)
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
public AuthenticationProvider samlAuthenticationProvider() {
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
|
new OpenSaml4AuthenticationProvider();
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
authenticationProvider.setResponseAuthenticationConverter(convertResponseToAuthentication);
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
return authenticationProvider;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ClientRegistration> googleClientRegistration() {
|
// @Bean
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
// public AuthenticationProvider daoAuthenticationProvider() {
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
// DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
return Optional.empty();
|
// provider.setUserDetailsService(userDetailsService); // UserDetailsService
|
||||||
}
|
// provider.setPasswordEncoder(passwordEncoder()); // PasswordEncoder
|
||||||
Client client = oauth.getClient();
|
// return provider;
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
@Bean
|
||||||
@ConditionalOnProperty(
|
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
||||||
value = "security.oauth2.enabled",
|
AuthenticationManagerBuilder authenticationManagerBuilder =
|
||||||
havingValue = "true",
|
http.getSharedObject(AuthenticationManagerBuilder.class);
|
||||||
matchIfMissing = false)
|
|
||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
|
||||||
return (authorities) -> {
|
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
|
||||||
|
|
||||||
authorities.forEach(
|
// authenticationManagerBuilder =
|
||||||
authority -> {
|
// authenticationManagerBuilder.authenticationProvider(
|
||||||
// Add existing OAUTH2 Authorities
|
// daoAuthenticationProvider()); // Benutzername/Passwort
|
||||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
|
||||||
|
|
||||||
// Add Authorities from database for existing user, if user is present.
|
if (applicationProperties.getSecurity().getSaml() != null
|
||||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
&& applicationProperties.getSecurity().getSaml().getEnabled()) {
|
||||||
String useAsUsername =
|
authenticationManagerBuilder.authenticationProvider(
|
||||||
applicationProperties
|
samlAuthenticationProvider()); // SAML
|
||||||
.getSecurity()
|
}
|
||||||
.getOAUTH2()
|
return authenticationManagerBuilder.build();
|
||||||
.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;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -386,4 +281,14 @@ public class SecurityConfiguration {
|
|||||||
public boolean activSecurity() {
|
public boolean activSecurity() {
|
||||||
return true;
|
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.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -30,13 +29,18 @@ import stirling.software.SPDF.model.User;
|
|||||||
@Component
|
@Component
|
||||||
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
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;
|
public UserAuthenticationFilter(
|
||||||
|
@Lazy UserService userService,
|
||||||
@Autowired
|
SessionPersistentRegistry sessionPersistentRegistry,
|
||||||
@Qualifier("loginEnabled")
|
@Qualifier("loginEnabled") boolean loginEnabledValue) {
|
||||||
public boolean loginEnabledValue;
|
this.userService = userService;
|
||||||
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
|
this.loginEnabledValue = loginEnabledValue;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(
|
protected void doFilterInternal(
|
||||||
@@ -51,6 +55,19 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
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
|
// Check for API key in the request headers if no authentication exists
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-Key");
|
||||||
@@ -159,7 +176,10 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (String pattern : permitAllPatterns) {
|
for (String pattern : permitAllPatterns) {
|
||||||
if (uri.startsWith(pattern) || uri.endsWith(".svg")) {
|
if (uri.startsWith(pattern)
|
||||||
|
|| uri.endsWith(".svg")
|
||||||
|
|| uri.endsWith(".png")
|
||||||
|
|| uri.endsWith(".ico")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
|||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.session.SessionInformation;
|
import org.springframework.security.core.session.SessionInformation;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
@@ -18,7 +19,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Service;
|
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.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
@@ -43,6 +44,10 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
@Autowired DatabaseBackupInterface databaseBackupHelper;
|
@Autowired DatabaseBackupInterface databaseBackupHelper;
|
||||||
|
|
||||||
|
public long getTotalUserCount() {
|
||||||
|
return userRepository.count();
|
||||||
|
}
|
||||||
|
|
||||||
// Handle OAUTH2 login and user auto creation.
|
// Handle OAUTH2 login and user auto creation.
|
||||||
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
||||||
throws IllegalArgumentException, IOException {
|
throws IllegalArgumentException, IOException {
|
||||||
@@ -342,4 +347,14 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCurrentUsername() {
|
||||||
|
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
|
||||||
|
if (principal instanceof UserDetails) {
|
||||||
|
return ((UserDetails) principal).getUsername();
|
||||||
|
} else {
|
||||||
|
return principal.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||||
import stirling.software.SPDF.utils.FileInfo;
|
import stirling.software.SPDF.utils.FileInfo;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
// Redirect to the original destination
|
// Redirect to the original destination
|
||||||
super.onAuthenticationSuccess(request, response, authentication);
|
super.onAuthenticationSuccess(request, response, authentication);
|
||||||
} else {
|
} else {
|
||||||
OAUTH2 oAuth = applicationProperties.getSecurity().getOAUTH2();
|
OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
if (loginAttemptService.isBlocked(username)) {
|
if (loginAttemptService.isBlocked(username)) {
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
if (authentication instanceof OAuth2AuthenticationToken) {
|
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||||
OAUTH2 oauth2 = applicationProperties.getSecurity().getOAUTH2();
|
OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();
|
||||||
String usernameAttribute = oauth2.getUseAsUsername();
|
String usernameAttribute = oauth2.getUseAsUsername();
|
||||||
if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) {
|
if (usernameAttribute == null || usernameAttribute.trim().isEmpty()) {
|
||||||
Client client = oauth2.getClient();
|
Client client = oauth2.getClient();
|
||||||
|
|||||||
@@ -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
|
@Slf4j
|
||||||
public class CustomHttpSessionListener implements HttpSessionListener {
|
public class CustomHttpSessionListener implements HttpSessionListener {
|
||||||
|
|
||||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
private SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) {
|
||||||
|
super();
|
||||||
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sessionCreated(HttpSessionEvent se) {
|
public void sessionCreated(HttpSessionEvent se) {}
|
||||||
log.info("Session created: " + se.getSession().getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sessionDestroyed(HttpSessionEvent se) {
|
public void sessionDestroyed(HttpSessionEvent se) {
|
||||||
log.info("Session destroyed: " + se.getSession().getId());
|
|
||||||
sessionPersistentRegistry.expireSession(se.getSession().getId());
|
sessionPersistentRegistry.expireSession(se.getSession().getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,14 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (principalName != null) {
|
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 sessionEntity = new SessionEntity();
|
||||||
sessionEntity.setSessionId(sessionId);
|
sessionEntity.setSessionId(sessionId);
|
||||||
sessionEntity.setPrincipalName(principalName);
|
sessionEntity.setPrincipalName(principalName);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
|||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -23,6 +24,8 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.CropPdfForm;
|
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;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -32,6 +35,17 @@ public class CropController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
private final PostHogService postHogService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CropController(
|
||||||
|
CustomPDDocumentFactory pdfDocumentFactory, PostHogService postHogService) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
this.postHogService = postHogService;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Crops a PDF document",
|
summary = "Crops a PDF document",
|
||||||
@@ -40,7 +54,8 @@ public class CropController {
|
|||||||
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
|
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
|
||||||
PDDocument sourceDocument = Loader.loadPDF(form.getFileInput().getBytes());
|
PDDocument sourceDocument = Loader.loadPDF(form.getFileInput().getBytes());
|
||||||
|
|
||||||
PDDocument newDocument = new PDDocument();
|
PDDocument newDocument =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||||
|
|
||||||
int totalPages = sourceDocument.getNumberOfPages();
|
int totalPages = sourceDocument.getNumberOfPages();
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -33,6 +34,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
|
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -43,9 +45,16 @@ public class MergeController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public MergeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
// Merges a list of PDDocument objects into a single PDDocument
|
// Merges a list of PDDocument objects into a single PDDocument
|
||||||
public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
public PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
||||||
PDDocument mergedDoc = new PDDocument();
|
PDDocument mergedDoc = pdfDocumentFactory.createNewDocument();
|
||||||
for (PDDocument doc : documents) {
|
for (PDDocument doc : documents) {
|
||||||
for (PDPage page : doc.getPages()) {
|
for (PDPage page : doc.getPages()) {
|
||||||
mergedDoc.addPage(page);
|
mergedDoc.addPage(page);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
|||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -26,6 +27,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
|
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -35,6 +37,13 @@ public class MultiPageLayoutController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public MultiPageLayoutController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Merge multiple pages of a PDF document into a single page",
|
summary = "Merge multiple pages of a PDF document into a single page",
|
||||||
@@ -60,7 +69,8 @@ public class MultiPageLayoutController {
|
|||||||
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
|
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
|
||||||
|
|
||||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
PDDocument newDocument = new PDDocument();
|
PDDocument newDocument =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||||
PDPage newPage = new PDPage(PDRectangle.A4);
|
PDPage newPage = new PDPage(PDRectangle.A4);
|
||||||
newDocument.addPage(newPage);
|
newDocument.addPage(newPage);
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,15 @@ package stirling.software.SPDF.controller.api;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.service.PdfImageRemovalService;
|
import stirling.software.SPDF.service.PdfImageRemovalService;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -25,15 +24,21 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
public class PdfImageRemovalController {
|
public class PdfImageRemovalController {
|
||||||
|
|
||||||
// Service for removing images from PDFs
|
// Service for removing images from PDFs
|
||||||
@Autowired private PdfImageRemovalService pdfImageRemovalService;
|
private final PdfImageRemovalService pdfImageRemovalService;
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for dependency injection of PdfImageRemovalService.
|
* Constructor for dependency injection of PdfImageRemovalService.
|
||||||
*
|
*
|
||||||
* @param pdfImageRemovalService The service used for removing images from PDFs.
|
* @param pdfImageRemovalService The service used for removing images from PDFs.
|
||||||
*/
|
*/
|
||||||
public PdfImageRemovalController(PdfImageRemovalService pdfImageRemovalService) {
|
@Autowired
|
||||||
|
public PdfImageRemovalController(
|
||||||
|
PdfImageRemovalService pdfImageRemovalService,
|
||||||
|
CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
this.pdfImageRemovalService = pdfImageRemovalService;
|
this.pdfImageRemovalService = pdfImageRemovalService;
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,14 +58,8 @@ public class PdfImageRemovalController {
|
|||||||
description =
|
description =
|
||||||
"This endpoint remove images from file to reduce the file size.Input:PDF Output:PDF Type:MISO")
|
"This endpoint remove images from file to reduce the file size.Input:PDF Output:PDF Type:MISO")
|
||||||
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException {
|
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException {
|
||||||
|
// Load the PDF document
|
||||||
MultipartFile pdf = file.getFileInput();
|
PDDocument document = pdfDocumentFactory.load(file);
|
||||||
|
|
||||||
// Convert the MultipartFile to a byte array
|
|
||||||
byte[] pdfBytes = pdf.getBytes();
|
|
||||||
|
|
||||||
// Load the PDF document from the byte array
|
|
||||||
PDDocument document = Loader.loadPDF(pdfBytes);
|
|
||||||
|
|
||||||
// Remove images from the PDF document using the service
|
// Remove images from the PDF document using the service
|
||||||
PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document);
|
PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document);
|
||||||
@@ -74,7 +73,8 @@ public class PdfImageRemovalController {
|
|||||||
|
|
||||||
// Generate a new filename for the modified PDF
|
// Generate a new filename for the modified PDF
|
||||||
String mergedFileName =
|
String mergedFileName =
|
||||||
pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_images.pdf";
|
file.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
||||||
|
+ "_removed_images.pdf";
|
||||||
|
|
||||||
// Convert the byte array to a web response and return it
|
// Convert the byte array to a web response and return it
|
||||||
return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);
|
return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import java.util.Map;
|
|||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.multipdf.Overlay;
|
import org.apache.pdfbox.multipdf.Overlay;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -25,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
|
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -33,6 +35,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class PdfOverlayController {
|
public class PdfOverlayController {
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PdfOverlayController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
|
@PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Overlay PDF files in various modes",
|
summary = "Overlay PDF files in various modes",
|
||||||
@@ -56,7 +65,7 @@ public class PdfOverlayController {
|
|||||||
// "FixedRepeatOverlay"
|
// "FixedRepeatOverlay"
|
||||||
int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode
|
int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode
|
||||||
|
|
||||||
try (PDDocument basePdf = Loader.loadPDF(baseFile.getBytes());
|
try (PDDocument basePdf = pdfDocumentFactory.load(baseFile);
|
||||||
Overlay overlay = new Overlay()) {
|
Overlay overlay = new Overlay()) {
|
||||||
Map<Integer, String> overlayGuide =
|
Map<Integer, String> overlayGuide =
|
||||||
prepareOverlayGuide(
|
prepareOverlayGuide(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -24,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import stirling.software.SPDF.model.SortTypes;
|
import stirling.software.SPDF.model.SortTypes;
|
||||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
|
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -34,6 +36,13 @@ public class RearrangePagesPDFController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public RearrangePagesPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove pages from a PDF file",
|
summary = "Remove pages from a PDF file",
|
||||||
@@ -45,7 +54,7 @@ public class RearrangePagesPDFController {
|
|||||||
MultipartFile pdfFile = request.getFileInput();
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
String pagesToDelete = request.getPageNumbers();
|
String pagesToDelete = request.getPageNumbers();
|
||||||
|
|
||||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
||||||
|
|
||||||
// Split the page order string into an array of page numbers or range of numbers
|
// Split the page order string into an array of page numbers or range of numbers
|
||||||
String[] pageOrderArr = pagesToDelete.split(",");
|
String[] pageOrderArr = pagesToDelete.split(",");
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package stirling.software.SPDF.controller.api;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.RotatePDFRequest;
|
import stirling.software.SPDF.model.api.general.RotatePDFRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -29,6 +30,13 @@ public class RotationController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public RotationController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Rotate a PDF file",
|
summary = "Rotate a PDF file",
|
||||||
@@ -39,7 +47,7 @@ public class RotationController {
|
|||||||
MultipartFile pdfFile = request.getFileInput();
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
Integer angle = request.getAngle();
|
Integer angle = request.getAngle();
|
||||||
// Load the PDF document
|
// Load the PDF document
|
||||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
PDDocument document = pdfDocumentFactory.load(request);
|
||||||
|
|
||||||
// Get the list of pages in the document
|
// Get the list of pages in the document
|
||||||
PDPageTree pages = document.getPages();
|
PDPageTree pages = document.getPages();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
|||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -27,6 +28,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.ScalePagesRequest;
|
import stirling.software.SPDF.model.api.general.ScalePagesRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -36,6 +38,13 @@ public class ScalePagesController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ScalePagesController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Change the size of a PDF page/document",
|
summary = "Change the size of a PDF page/document",
|
||||||
@@ -47,29 +56,11 @@ public class ScalePagesController {
|
|||||||
String targetPDRectangle = request.getPageSize();
|
String targetPDRectangle = request.getPageSize();
|
||||||
float scaleFactor = request.getScaleFactor();
|
float scaleFactor = request.getScaleFactor();
|
||||||
|
|
||||||
Map<String, PDRectangle> sizeMap = new HashMap<>();
|
|
||||||
// Add A0 - A10
|
|
||||||
sizeMap.put("A0", PDRectangle.A0);
|
|
||||||
sizeMap.put("A1", PDRectangle.A1);
|
|
||||||
sizeMap.put("A2", PDRectangle.A2);
|
|
||||||
sizeMap.put("A3", PDRectangle.A3);
|
|
||||||
sizeMap.put("A4", PDRectangle.A4);
|
|
||||||
sizeMap.put("A5", PDRectangle.A5);
|
|
||||||
sizeMap.put("A6", PDRectangle.A6);
|
|
||||||
|
|
||||||
// Add other sizes
|
|
||||||
sizeMap.put("LETTER", PDRectangle.LETTER);
|
|
||||||
sizeMap.put("LEGAL", PDRectangle.LEGAL);
|
|
||||||
|
|
||||||
if (!sizeMap.containsKey(targetPDRectangle)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10");
|
|
||||||
}
|
|
||||||
|
|
||||||
PDRectangle targetSize = sizeMap.get(targetPDRectangle);
|
|
||||||
|
|
||||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
PDDocument outputDocument = new PDDocument();
|
PDDocument outputDocument =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||||
|
|
||||||
|
PDRectangle targetSize = getTargetSize(targetPDRectangle, sourceDocument);
|
||||||
|
|
||||||
int totalPages = sourceDocument.getNumberOfPages();
|
int totalPages = sourceDocument.getNumberOfPages();
|
||||||
for (int i = 0; i < totalPages; i++) {
|
for (int i = 0; i < totalPages; i++) {
|
||||||
@@ -116,4 +107,45 @@ public class ScalePagesController {
|
|||||||
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
||||||
+ "_scaled.pdf");
|
+ "_scaled.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
|
||||||
|
if (targetPDRectangle.equals("KEEP")) {
|
||||||
|
if (sourceDocument.getNumberOfPages() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the first page to determine the target page size
|
||||||
|
PDPage sourcePage = sourceDocument.getPage(0);
|
||||||
|
PDRectangle sourceSize = sourcePage.getMediaBox();
|
||||||
|
|
||||||
|
return sourceSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, PDRectangle> sizeMap = getSizeMap();
|
||||||
|
|
||||||
|
if (sizeMap.containsKey(targetPDRectangle)) {
|
||||||
|
return sizeMap.get(targetPDRectangle);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, LETTER, LEGAL, KEEP");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, PDRectangle> getSizeMap() {
|
||||||
|
Map<String, PDRectangle> sizeMap = new HashMap<>();
|
||||||
|
// Add A0 - A6
|
||||||
|
sizeMap.put("A0", PDRectangle.A0);
|
||||||
|
sizeMap.put("A1", PDRectangle.A1);
|
||||||
|
sizeMap.put("A2", PDRectangle.A2);
|
||||||
|
sizeMap.put("A3", PDRectangle.A3);
|
||||||
|
sizeMap.put("A4", PDRectangle.A4);
|
||||||
|
sizeMap.put("A5", PDRectangle.A5);
|
||||||
|
sizeMap.put("A6", PDRectangle.A6);
|
||||||
|
|
||||||
|
// Add other sizes
|
||||||
|
sizeMap.put("LETTER", PDRectangle.LETTER);
|
||||||
|
sizeMap.put("LEGAL", PDRectangle.LEGAL);
|
||||||
|
|
||||||
|
return sizeMap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -27,9 +28,8 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.PdfMetadata;
|
|
||||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -38,6 +38,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
public class SplitPDFController {
|
public class SplitPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SplitPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -51,11 +57,9 @@ public class SplitPDFController {
|
|||||||
// open the pdf document
|
// open the pdf document
|
||||||
|
|
||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
|
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
||||||
int totalPages = document.getNumberOfPages();
|
int totalPages = document.getNumberOfPages();
|
||||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||||
System.out.println(
|
|
||||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
|
||||||
if (!pageNumbers.contains(totalPages - 1)) {
|
if (!pageNumbers.contains(totalPages - 1)) {
|
||||||
// Create a mutable ArrayList so we can add to it
|
// Create a mutable ArrayList so we can add to it
|
||||||
pageNumbers = new ArrayList<>(pageNumbers);
|
pageNumbers = new ArrayList<>(pageNumbers);
|
||||||
@@ -70,7 +74,8 @@ public class SplitPDFController {
|
|||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
int previousPageNumber = 0;
|
int previousPageNumber = 0;
|
||||||
for (int splitPoint : pageNumbers) {
|
for (int splitPoint : pageNumbers) {
|
||||||
try (PDDocument splitDocument = new PDDocument()) {
|
try (PDDocument splitDocument =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
|
||||||
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
||||||
PDPage page = document.getPage(i);
|
PDPage page = document.getPage(i);
|
||||||
splitDocument.addPage(page);
|
splitDocument.addPage(page);
|
||||||
@@ -79,7 +84,7 @@ public class SplitPDFController {
|
|||||||
previousPageNumber = splitPoint + 1;
|
previousPageNumber = splitPoint + 1;
|
||||||
|
|
||||||
// Transfer metadata to split pdf
|
// Transfer metadata to split pdf
|
||||||
PdfUtils.setMetadataToPdf(splitDocument, metadata);
|
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
splitDocument.save(baos);
|
splitDocument.save(baos);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocume
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
|
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -33,7 +34,7 @@ import lombok.EqualsAndHashCode;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import stirling.software.SPDF.model.PdfMetadata;
|
import stirling.software.SPDF.model.PdfMetadata;
|
||||||
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
|
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.service.PdfMetadataService;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -44,6 +45,13 @@ public class SplitPdfByChaptersController {
|
|||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(SplitPdfByChaptersController.class);
|
LoggerFactory.getLogger(SplitPdfByChaptersController.class);
|
||||||
|
|
||||||
|
private final PdfMetadataService pdfMetadataService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SplitPdfByChaptersController(PdfMetadataService pdfMetadataService) {
|
||||||
|
this.pdfMetadataService = pdfMetadataService;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
|
@PostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Split PDFs by Chapters",
|
summary = "Split PDFs by Chapters",
|
||||||
@@ -59,15 +67,6 @@ public class SplitPdfByChaptersController {
|
|||||||
}
|
}
|
||||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
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();
|
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
||||||
|
|
||||||
if (outline == null) {
|
if (outline == null) {
|
||||||
@@ -258,7 +257,7 @@ public class SplitPdfByChaptersController {
|
|||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
PdfMetadata metadata = null;
|
PdfMetadata metadata = null;
|
||||||
if (includeMetadata) {
|
if (includeMetadata) {
|
||||||
metadata = PdfUtils.extractMetadataFromPdf(sourceDocument);
|
metadata = pdfMetadataService.extractMetadataFromPdf(sourceDocument);
|
||||||
}
|
}
|
||||||
for (Bookmark bookmark : bookmarks) {
|
for (Bookmark bookmark : bookmarks) {
|
||||||
try (PDDocument splitDocument = new PDDocument()) {
|
try (PDDocument splitDocument = new PDDocument()) {
|
||||||
@@ -273,7 +272,7 @@ public class SplitPdfByChaptersController {
|
|||||||
}
|
}
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
if (includeMetadata) {
|
if (includeMetadata) {
|
||||||
PdfUtils.setMetadataToPdf(splitDocument, metadata);
|
pdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
splitDocument.save(baos);
|
splitDocument.save(baos);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
|||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -33,6 +34,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
|
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -43,6 +45,13 @@ public class SplitPdfBySectionsController {
|
|||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(SplitPdfBySectionsController.class);
|
LoggerFactory.getLogger(SplitPdfBySectionsController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SplitPdfBySectionsController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
|
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Split PDF pages into smaller sections",
|
summary = "Split PDF pages into smaller sections",
|
||||||
@@ -65,7 +74,7 @@ public class SplitPdfBySectionsController {
|
|||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "");
|
.replaceFirst("[.][^.]+$", "");
|
||||||
if (merge) {
|
if (merge) {
|
||||||
MergeController mergeController = new MergeController();
|
MergeController mergeController = new MergeController(pdfDocumentFactory);
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
mergeController.mergeDocuments(splitDocuments).save(baos);
|
mergeController.mergeDocuments(splitDocuments).save(baos);
|
||||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf");
|
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf");
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -25,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
|
import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -34,6 +36,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
public class SplitPdfBySizeController {
|
public class SplitPdfBySizeController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySizeController.class);
|
private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySizeController.class);
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SplitPdfBySizeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
|
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -84,7 +92,8 @@ public class SplitPdfBySizeController {
|
|||||||
PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename)
|
PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
long currentSize = 0;
|
long currentSize = 0;
|
||||||
PDDocument currentDoc = new PDDocument();
|
PDDocument currentDoc =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||||
int fileIndex = 1;
|
int fileIndex = 1;
|
||||||
|
|
||||||
for (int pageIndex = 0; pageIndex < sourceDocument.getNumberOfPages(); pageIndex++) {
|
for (int pageIndex = 0; pageIndex < sourceDocument.getNumberOfPages(); pageIndex++) {
|
||||||
@@ -121,7 +130,8 @@ public class SplitPdfBySizeController {
|
|||||||
PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename)
|
PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
int currentPageCount = 0;
|
int currentPageCount = 0;
|
||||||
PDDocument currentDoc = new PDDocument();
|
PDDocument currentDoc =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||||
int fileIndex = 1;
|
int fileIndex = 1;
|
||||||
for (PDPage page : sourceDocument.getPages()) {
|
for (PDPage page : sourceDocument.getPages()) {
|
||||||
currentDoc.addPage(page);
|
currentDoc.addPage(page);
|
||||||
@@ -152,7 +162,8 @@ public class SplitPdfBySizeController {
|
|||||||
int currentPageIndex = 0;
|
int currentPageIndex = 0;
|
||||||
int fileIndex = 1;
|
int fileIndex = 1;
|
||||||
for (int i = 0; i < documentCount; i++) {
|
for (int i = 0; i < documentCount; i++) {
|
||||||
PDDocument currentDoc = new PDDocument();
|
PDDocument currentDoc =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||||
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
|
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
|
||||||
|
|
||||||
for (int j = 0; j < pagesToAdd; j++) {
|
for (int j = 0; j < pagesToAdd; j++) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
|||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -23,6 +24,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -32,6 +34,13 @@ public class ToSinglePageController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ToSinglePageController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a multi-page PDF into a single long page PDF",
|
summary = "Convert a multi-page PDF into a single long page PDF",
|
||||||
@@ -53,7 +62,8 @@ public class ToSinglePageController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create new document and page with calculated dimensions
|
// Create new document and page with calculated dimensions
|
||||||
PDDocument newDocument = new PDDocument();
|
PDDocument newDocument =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);
|
||||||
PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight));
|
PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight));
|
||||||
newDocument.addPage(newPage);
|
newDocument.addPage(newPage);
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
@@ -40,6 +41,7 @@ import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
|||||||
@Controller
|
@Controller
|
||||||
@Tag(name = "User", description = "User APIs")
|
@Tag(name = "User", description = "User APIs")
|
||||||
@RequestMapping("/api/v1/user")
|
@RequestMapping("/api/v1/user")
|
||||||
|
@Slf4j
|
||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
@Autowired private UserService userService;
|
@Autowired private UserService userService;
|
||||||
@@ -191,13 +193,11 @@ public class UserController {
|
|||||||
Map<String, String[]> paramMap = request.getParameterMap();
|
Map<String, String[]> paramMap = request.getParameterMap();
|
||||||
Map<String, String> updates = new HashMap<>();
|
Map<String, String> updates = new HashMap<>();
|
||||||
|
|
||||||
System.out.println("Received parameter map: " + paramMap);
|
|
||||||
|
|
||||||
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||||
updates.put(entry.getKey(), entry.getValue()[0]);
|
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
|
// Assuming you have a method in userService to update the settings for a user
|
||||||
userService.updateUserSettings(principal.getName(), updates);
|
userService.updateUserSettings(principal.getName(), updates);
|
||||||
@@ -208,7 +208,7 @@ public class UserController {
|
|||||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
@PostMapping("/admin/saveUser")
|
@PostMapping("/admin/saveUser")
|
||||||
public RedirectView saveUser(
|
public RedirectView saveUser(
|
||||||
@RequestParam String username,
|
@RequestParam(name = "username", required = true) String username,
|
||||||
@RequestParam(name = "password", required = false) String password,
|
@RequestParam(name = "password", required = false) String password,
|
||||||
@RequestParam(name = "role") String role,
|
@RequestParam(name = "role") String role,
|
||||||
@RequestParam(name = "authType") String authType,
|
@RequestParam(name = "authType") String authType,
|
||||||
|
|||||||
@@ -5,26 +5,32 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.github.pixee.security.Filenames;
|
import io.github.pixee.security.Filenames;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
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.model.api.GeneralFile;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.FileToPdf;
|
import stirling.software.SPDF.utils.FileToPdf;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
// @RestController
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
// @Tag(name = "Convert", description = "Convert APIs")
|
||||||
@RequestMapping("/api/v1/convert")
|
// @RequestMapping("/api/v1/convert")
|
||||||
public class ConvertBookToPDFController {
|
public class ConvertBookToPDFController {
|
||||||
|
|
||||||
|
private final boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
public ConvertBookToPDFController(
|
||||||
private boolean bookAndHtmlFormatsInstalled;
|
CustomPDDocumentFactory pdfDocumentFactory,
|
||||||
|
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -60,6 +66,8 @@ public class ConvertBookToPDFController {
|
|||||||
}
|
}
|
||||||
byte[] pdfBytes = FileToPdf.convertBookTypeToPdf(fileInput.getBytes(), originalFilename);
|
byte[] pdfBytes = FileToPdf.convertBookTypeToPdf(fileInput.getBytes(), originalFilename);
|
||||||
|
|
||||||
|
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
+ ".pdf"; // Remove file extension and append .pdf
|
+ ".pdf"; // Remove file extension and append .pdf
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
|
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.FileToPdf;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -22,9 +23,17 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertHtmlToPDF {
|
public class ConvertHtmlToPDF {
|
||||||
|
|
||||||
|
private final boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
public ConvertHtmlToPDF(
|
||||||
private boolean bookAndHtmlFormatsInstalled;
|
CustomPDDocumentFactory pdfDocumentFactory,
|
||||||
|
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -52,6 +61,8 @@ public class ConvertHtmlToPDF {
|
|||||||
originalFilename,
|
originalFilename,
|
||||||
bookAndHtmlFormatsInstalled);
|
bookAndHtmlFormatsInstalled);
|
||||||
|
|
||||||
|
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
+ ".pdf"; // Remove file extension and append .pdf
|
+ ".pdf"; // Remove file extension and append .pdf
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.apache.commons.io.FileUtils;
|
|||||||
import org.apache.pdfbox.rendering.ImageType;
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -30,6 +31,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
|
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
|
||||||
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.CheckProgramInstall;
|
import stirling.software.SPDF.utils.CheckProgramInstall;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
@@ -43,6 +45,13 @@ public class ConvertImgPDFController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ConvertImgPDFController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/img")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/img")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert PDF to image(s)",
|
summary = "Convert PDF to image(s)",
|
||||||
@@ -178,7 +187,8 @@ public class ConvertImgPDFController {
|
|||||||
boolean autoRotate = request.isAutoRotate();
|
boolean autoRotate = request.isAutoRotate();
|
||||||
|
|
||||||
// Convert the file to PDF and get the resulting bytes
|
// Convert the file to PDF and get the resulting bytes
|
||||||
byte[] bytes = PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType);
|
byte[] bytes =
|
||||||
|
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
bytes,
|
bytes,
|
||||||
file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
|
file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.GeneralFile;
|
import stirling.software.SPDF.model.api.GeneralFile;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.FileToPdf;
|
import stirling.software.SPDF.utils.FileToPdf;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -32,9 +33,17 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertMarkdownToPdf {
|
public class ConvertMarkdownToPdf {
|
||||||
|
|
||||||
|
private final boolean bookAndHtmlFormatsInstalled;
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("bookAndHtmlFormatsInstalled")
|
public ConvertMarkdownToPdf(
|
||||||
private boolean bookAndHtmlFormatsInstalled;
|
CustomPDDocumentFactory pdfDocumentFactory,
|
||||||
|
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -73,7 +82,7 @@ public class ConvertMarkdownToPdf {
|
|||||||
htmlContent.getBytes(),
|
htmlContent.getBytes(),
|
||||||
"converted.html",
|
"converted.html",
|
||||||
bookAndHtmlFormatsInstalled);
|
bookAndHtmlFormatsInstalled);
|
||||||
|
pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
+ ".pdf"; // Remove file extension and append .pdf
|
+ ".pdf"; // Remove file extension and append .pdf
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -8,6 +9,8 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -20,6 +23,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.GeneralFile;
|
import stirling.software.SPDF.model.api.GeneralFile;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@@ -29,7 +33,7 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertOfficeController {
|
public class ConvertOfficeController {
|
||||||
|
|
||||||
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||||
// Check for valid file extension
|
// Check for valid file extension
|
||||||
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||||
if (originalFilename == null
|
if (originalFilename == null
|
||||||
@@ -62,12 +66,10 @@ public class ConvertOfficeController {
|
|||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the converted PDF file
|
// Read the converted PDF file
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
return tempOutputFile.toFile();
|
||||||
return pdfBytes;
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||||
Files.deleteIfExists(tempOutputFile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +78,13 @@ public class ConvertOfficeController {
|
|||||||
return fileExtension.matches(extensionPattern);
|
return fileExtension.matches(extensionPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ConvertOfficeController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/file/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/file/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a file to a PDF using LibreOffice",
|
summary = "Convert a file to a PDF using LibreOffice",
|
||||||
@@ -86,12 +95,18 @@ public class ConvertOfficeController {
|
|||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
// unused but can start server instance if startup time is to long
|
// unused but can start server instance if startup time is to long
|
||||||
// LibreOfficeListener.getInstance().start();
|
// LibreOfficeListener.getInstance().start();
|
||||||
|
File file = null;
|
||||||
|
try {
|
||||||
|
file = convertToPdf(inputFile);
|
||||||
|
|
||||||
byte[] pdfByteArray = convertToPdf(inputFile);
|
PDDocument doc = pdfDocumentFactory.load(file);
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
pdfByteArray,
|
doc,
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_convertedToPDF.pdf");
|
+ "_convertedToPDF.pdf");
|
||||||
|
} finally {
|
||||||
|
if (file != null) file.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,22 +11,19 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.github.pixee.security.Filenames;
|
import io.github.pixee.security.Filenames;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.converters.PdfToBookRequest;
|
import stirling.software.SPDF.model.api.converters.PdfToBookRequest;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
// @RestController
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
// @Tag(name = "Convert", description = "Convert APIs")
|
||||||
@RequestMapping("/api/v1/convert")
|
// @RequestMapping("/api/v1/convert")
|
||||||
public class ConvertPDFToBookController {
|
public class ConvertPDFToBookController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -53,32 +46,7 @@ public class ConvertPDFToPDFA {
|
|||||||
// Convert MultipartFile to byte[]
|
// Convert MultipartFile to byte[]
|
||||||
byte[] pdfBytes = inputFile.getBytes();
|
byte[] pdfBytes = inputFile.getBytes();
|
||||||
|
|
||||||
// Load the PDF document
|
// Save the uploaded file to a temporary location
|
||||||
PDDocument document = Loader.loadPDF(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
|
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) {
|
try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) {
|
||||||
outputStream.write(pdfBytes);
|
outputStream.write(pdfBytes);
|
||||||
@@ -87,32 +55,41 @@ public class ConvertPDFToPDFA {
|
|||||||
// Prepare the output file path
|
// Prepare the output file path
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
|
||||||
// Prepare the OCRmyPDF command
|
// Prepare the ghostscript command
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add("ocrmypdf");
|
command.add("gs");
|
||||||
command.add("--skip-text");
|
command.add("-dPDFA=" + ("pdfa".equals(outputFormat) ? "2" : "1"));
|
||||||
command.add("--tesseract-timeout=0");
|
command.add("-dNOPAUSE");
|
||||||
command.add("--output-type");
|
command.add("-dBATCH");
|
||||||
command.add(outputFormat.toString());
|
command.add("-sColorConversionStrategy=sRGB");
|
||||||
command.add(tempInputFile.toString());
|
command.add("-sDEVICE=pdfwrite");
|
||||||
|
command.add("-dPDFACompatibilityPolicy=2");
|
||||||
|
command.add("-o");
|
||||||
command.add(tempOutputFile.toString());
|
command.add(tempOutputFile.toString());
|
||||||
|
command.add(tempInputFile.toString());
|
||||||
|
|
||||||
ProcessExecutorResult returnCode =
|
ProcessExecutorResult returnCode =
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the optimized PDF file
|
if (returnCode.getRc() != 0) {
|
||||||
byte[] optimizedPdfBytes = Files.readAllBytes(tempOutputFile);
|
logger.info(
|
||||||
|
outputFormat + " conversion failed with return code: " + returnCode.getRc());
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up the temporary files
|
try {
|
||||||
Files.deleteIfExists(tempInputFile);
|
byte[] pdfBytesOutput = Files.readAllBytes(tempOutputFile);
|
||||||
Files.deleteIfExists(tempOutputFile);
|
// Return the optimized PDF as a response
|
||||||
|
String outputFilename =
|
||||||
// Return the optimized PDF as a response
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
String outputFilename =
|
.replaceFirst("[.][^.]+$", "")
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
+ "_PDFA.pdf";
|
||||||
.replaceFirst("[.][^.]+$", "")
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
+ "_PDFA.pdf";
|
pdfBytesOutput, outputFilename, MediaType.APPLICATION_PDF);
|
||||||
return WebResponseUtils.bytesToWebResponse(optimizedPdfBytes, outputFilename);
|
} finally {
|
||||||
|
// Clean up the temporary files
|
||||||
|
Files.deleteIfExists(tempInputFile);
|
||||||
|
Files.deleteIfExists(tempOutputFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import java.nio.file.Path;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -16,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
@@ -26,6 +31,15 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertWebsiteToPDF {
|
public class ConvertWebsiteToPDF {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ConvertWebsiteToPDF.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ConvertWebsiteToPDF(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a URL to a PDF",
|
summary = "Convert a URL to a PDF",
|
||||||
@@ -46,12 +60,12 @@ public class ConvertWebsiteToPDF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Path tempOutputFile = null;
|
Path tempOutputFile = null;
|
||||||
byte[] pdfBytes;
|
PDDocument doc = null;
|
||||||
try {
|
try {
|
||||||
// Prepare the output file path
|
// Prepare the output file path
|
||||||
tempOutputFile = Files.createTempFile("output_", ".pdf");
|
tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
|
||||||
// Prepare the OCRmyPDF command
|
// Prepare the WeasyPrint command
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add("weasyprint");
|
command.add("weasyprint");
|
||||||
command.add(URL);
|
command.add(URL);
|
||||||
@@ -61,16 +75,23 @@ public class ConvertWebsiteToPDF {
|
|||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the optimized PDF file
|
// Load the PDF using pdfDocumentFactory
|
||||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
doc = pdfDocumentFactory.load(tempOutputFile.toFile());
|
||||||
} finally {
|
|
||||||
// Clean up the temporary files
|
|
||||||
Files.deleteIfExists(tempOutputFile);
|
|
||||||
}
|
|
||||||
// Convert URL to a safe filename
|
|
||||||
String outputFilename = convertURLToFileName(URL);
|
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
// Convert URL to a safe filename
|
||||||
|
String outputFilename = convertURLToFileName(URL);
|
||||||
|
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);
|
||||||
|
} finally {
|
||||||
|
|
||||||
|
if (tempOutputFile != null) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(tempOutputFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error deleting temporary output file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String convertURLToFileName(String url) {
|
private String convertURLToFileName(String url) {
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ import stirling.software.SPDF.model.api.extract.PDFFilePage;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ExtractController {
|
public class ExtractCSVController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
||||||
|
|
||||||
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Extracts a PDF document to csv",
|
summary = "Extracts a CSV document from a PDF",
|
||||||
description =
|
description =
|
||||||
"This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
|
"This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
|
||||||
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
|
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception {
|
||||||
@@ -12,11 +12,11 @@ import java.util.List;
|
|||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
|
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -49,6 +50,13 @@ public class AutoSplitPdfController {
|
|||||||
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
|
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
|
||||||
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
|
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public AutoSplitPdfController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Auto split PDF pages into separate documents",
|
summary = "Auto split PDF pages into separate documents",
|
||||||
@@ -59,73 +67,93 @@ public class AutoSplitPdfController {
|
|||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
boolean duplexMode = request.isDuplexMode();
|
boolean duplexMode = request.isDuplexMode();
|
||||||
|
|
||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = null;
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
|
||||||
pdfRenderer.setSubsamplingAllowed(true);
|
|
||||||
List<PDDocument> splitDocuments = new ArrayList<>();
|
List<PDDocument> splitDocuments = new ArrayList<>();
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
Path zipFile = null;
|
||||||
|
byte[] data = null;
|
||||||
|
|
||||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
try {
|
||||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
|
document = pdfDocumentFactory.load(file.getInputStream());
|
||||||
String result = decodeQRCode(bim);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
|
pdfRenderer.setSubsamplingAllowed(true);
|
||||||
splitDocuments.add(new PDDocument());
|
|
||||||
|
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||||
|
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
|
||||||
|
String result = decodeQRCode(bim);
|
||||||
|
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
|
||||||
|
splitDocuments.add(new PDDocument());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!splitDocuments.isEmpty()
|
||||||
|
&& !QR_CONTENT.equals(result)
|
||||||
|
&& !QR_CONTENT_OLD.equals(result)) {
|
||||||
|
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
|
||||||
|
} else if (page == 0) {
|
||||||
|
PDDocument firstDocument = new PDDocument();
|
||||||
|
firstDocument.addPage(document.getPage(page));
|
||||||
|
splitDocuments.add(firstDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If duplexMode is true and current page is a divider, then skip next page
|
||||||
|
if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
|
||||||
|
page++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!splitDocuments.isEmpty()
|
// Remove split documents that have no pages
|
||||||
&& !QR_CONTENT.equals(result)
|
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
|
||||||
&& !QR_CONTENT_OLD.equals(result)) {
|
|
||||||
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
|
zipFile = Files.createTempFile("split_documents", ".zip");
|
||||||
} else if (page == 0) {
|
String filename =
|
||||||
PDDocument firstDocument = new PDDocument();
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
firstDocument.addPage(document.getPage(page));
|
.replaceFirst("[.][^.]+$", "");
|
||||||
splitDocuments.add(firstDocument);
|
|
||||||
|
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||||
|
for (int i = 0; i < splitDocuments.size(); i++) {
|
||||||
|
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||||
|
PDDocument splitDocument = splitDocuments.get(i);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
splitDocument.save(baos);
|
||||||
|
byte[] pdf = baos.toByteArray();
|
||||||
|
|
||||||
|
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||||
|
zipOut.putNextEntry(pdfEntry);
|
||||||
|
zipOut.write(pdf);
|
||||||
|
zipOut.closeEntry();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If duplexMode is true and current page is a divider, then skip next page
|
|
||||||
if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
|
|
||||||
page++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove split documents that have no pages
|
|
||||||
splitDocuments.removeIf(pdDocument -> pdDocument.getNumberOfPages() == 0);
|
|
||||||
|
|
||||||
for (PDDocument splitDocument : splitDocuments) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
splitDocument.save(baos);
|
|
||||||
splitDocumentsBoas.add(baos);
|
|
||||||
splitDocument.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.close();
|
|
||||||
|
|
||||||
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
|
||||||
String filename =
|
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
byte[] data;
|
|
||||||
|
|
||||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
|
||||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
|
||||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
|
||||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
|
||||||
byte[] pdf = baos.toByteArray();
|
|
||||||
|
|
||||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
|
||||||
zipOut.putNextEntry(pdfEntry);
|
|
||||||
zipOut.write(pdf);
|
|
||||||
zipOut.closeEntry();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("exception", e);
|
|
||||||
} finally {
|
|
||||||
data = Files.readAllBytes(zipFile);
|
data = Files.readAllBytes(zipFile);
|
||||||
Files.deleteIfExists(zipFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
} finally {
|
||||||
|
// Clean up resources
|
||||||
|
if (document != null) {
|
||||||
|
try {
|
||||||
|
document.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error closing main PDDocument", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PDDocument splitDoc : splitDocuments) {
|
||||||
|
try {
|
||||||
|
splitDoc.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error closing split PDDocument", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zipFile != null) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Error deleting temporary zip file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String decodeQRCode(BufferedImage bufferedImage) {
|
private static String decodeQRCode(BufferedImage bufferedImage) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.apache.pdfbox.rendering.PDFRenderer;
|
|||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -30,6 +31,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
|
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -40,6 +42,13 @@ public class BlankPageController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class);
|
private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public BlankPageController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove blank pages from a PDF file",
|
summary = "Remove blank pages from a PDF file",
|
||||||
@@ -124,7 +133,7 @@ public class BlankPageController {
|
|||||||
|
|
||||||
public void createZipEntry(ZipOutputStream zos, List<PDPage> pages, String entryName)
|
public void createZipEntry(ZipOutputStream zos, List<PDPage> pages, String entryName)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
try (PDDocument document = new PDDocument()) {
|
try (PDDocument document = pdfDocumentFactory.createNewDocument()) {
|
||||||
|
|
||||||
for (PDPage page : pages) {
|
for (PDPage page : pages) {
|
||||||
document.addPage(page);
|
document.addPage(page);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
|||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -32,6 +33,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
|
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
@@ -44,6 +46,13 @@ public class CompressController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CompressController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Optimize PDF file",
|
summary = "Optimize PDF file",
|
||||||
@@ -258,7 +267,7 @@ public class CompressController {
|
|||||||
}
|
}
|
||||||
// Read the optimized PDF file
|
// Read the optimized PDF file
|
||||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
Path finalFile = tempOutputFile;
|
||||||
// Check if optimized file is larger than the original
|
// Check if optimized file is larger than the original
|
||||||
if (pdfBytes.length > inputFileSize) {
|
if (pdfBytes.length > inputFileSize) {
|
||||||
// Log the occurrence
|
// Log the occurrence
|
||||||
@@ -266,14 +275,15 @@ public class CompressController {
|
|||||||
"Optimized file is larger than the original. Returning the original file instead.");
|
"Optimized file is larger than the original. Returning the original file instead.");
|
||||||
|
|
||||||
// Read the original file again
|
// Read the original file again
|
||||||
pdfBytes = Files.readAllBytes(tempInputFile);
|
finalFile = tempInputFile;
|
||||||
}
|
}
|
||||||
// Return the optimized PDF as a response
|
// Return the optimized PDF as a response
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_Optimized.pdf";
|
+ "_Optimized.pdf";
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
|
pdfDocumentFactory.load(finalFile.toFile()), outputFilename);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ public class ExtractImagesController {
|
|||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
String format = request.getFormat();
|
String format = request.getFormat();
|
||||||
boolean allowDuplicates = request.isAllowDuplicates();
|
boolean allowDuplicates = request.isAllowDuplicates();
|
||||||
System.out.println(
|
|
||||||
System.currentTimeMillis() + " file=" + file.getName() + ", format=" + format);
|
|
||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
|
|
||||||
// Determine if multithreading should be used based on PDF size or number of pages
|
// 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
|
// Iterate over each page
|
||||||
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
|
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
|
||||||
PDPage page = document.getPage(pgNum);
|
PDPage page = document.getPage(pgNum);
|
||||||
int pageNum = document.getPages().indexOf(page) + 1;
|
|
||||||
// Submit a task for processing each page
|
|
||||||
Future<Void> future =
|
Future<Void> future =
|
||||||
executor.submit(
|
executor.submit(
|
||||||
() -> {
|
() -> {
|
||||||
extractImagesFromPage(
|
// Use the page number directly from the iterator, so no need to
|
||||||
page,
|
// calculate manually
|
||||||
format,
|
int pageNum = document.getPages().indexOf(page) + 1;
|
||||||
filename,
|
|
||||||
pageNum,
|
try {
|
||||||
processedImages,
|
// Call the image extraction method for each page
|
||||||
zos,
|
extractImagesFromPage(
|
||||||
allowDuplicates);
|
page,
|
||||||
return null;
|
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);
|
futures.add(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.apache.pdfbox.rendering.ImageType;
|
|||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -25,9 +26,8 @@ import io.github.pixee.security.Filenames;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.PdfMetadata;
|
|
||||||
import stirling.software.SPDF.model.api.misc.FlattenRequest;
|
import stirling.software.SPDF.model.api.misc.FlattenRequest;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -37,6 +37,13 @@ public class FlattenController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FlattenController.class);
|
private static final Logger logger = LoggerFactory.getLogger(FlattenController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public FlattenController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/flatten")
|
@PostMapping(consumes = "multipart/form-data", value = "/flatten")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Flatten PDF form fields or full page",
|
summary = "Flatten PDF form fields or full page",
|
||||||
@@ -46,7 +53,6 @@ public class FlattenController {
|
|||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
|
|
||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
|
|
||||||
Boolean flattenOnlyForms = request.getFlattenOnlyForms();
|
Boolean flattenOnlyForms = request.getFlattenOnlyForms();
|
||||||
|
|
||||||
if (Boolean.TRUE.equals(flattenOnlyForms)) {
|
if (Boolean.TRUE.equals(flattenOnlyForms)) {
|
||||||
@@ -60,7 +66,8 @@ public class FlattenController {
|
|||||||
// flatten whole page aka convert each page to image and readd it (making text
|
// flatten whole page aka convert each page to image and readd it (making text
|
||||||
// unselectable)
|
// unselectable)
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
PDDocument newDocument = new PDDocument();
|
PDDocument newDocument =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document);
|
||||||
int numPages = document.getNumberOfPages();
|
int numPages = document.getNumberOfPages();
|
||||||
for (int i = 0; i < numPages; i++) {
|
for (int i = 0; i < numPages; i++) {
|
||||||
try {
|
try {
|
||||||
@@ -80,7 +87,6 @@ public class FlattenController {
|
|||||||
logger.error("exception", e);
|
logger.error("exception", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PdfUtils.setMetadataToPdf(newDocument, metadata);
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
newDocument, Filenames.toSimpleFileName(file.getOriginalFilename()));
|
newDocument, Filenames.toSimpleFileName(file.getOriginalFilename()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -28,6 +29,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
|
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@@ -52,6 +54,13 @@ public class OCRController {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public OCRController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Process a PDF file with OCR",
|
summary = "Process a PDF file with OCR",
|
||||||
@@ -175,7 +184,7 @@ public class OCRController {
|
|||||||
tempOutputFile = tempPdfWithoutImages;
|
tempOutputFile = tempPdfWithoutImages;
|
||||||
}
|
}
|
||||||
// Read the OCR processed PDF file
|
// Read the OCR processed PDF file
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
byte[] pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
|
||||||
|
|
||||||
// Return the OCR processed PDF as a response
|
// Return the OCR processed PDF as a response
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
@@ -196,7 +205,13 @@ public class OCRController {
|
|||||||
// Add PDF file to the zip
|
// Add PDF file to the zip
|
||||||
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
||||||
zipOut.putNextEntry(pdfEntry);
|
zipOut.putNextEntry(pdfEntry);
|
||||||
Files.copy(tempOutputFile, zipOut);
|
try (ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes)) {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = pdfInputStream.read(buffer)) != -1) {
|
||||||
|
zipOut.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
zipOut.closeEntry();
|
zipOut.closeEntry();
|
||||||
|
|
||||||
// Add text file to the zip
|
// Add text file to the zip
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -17,6 +18,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
|
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -27,6 +29,13 @@ public class OverlayImageController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public OverlayImageController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Overlay image onto a PDF file",
|
summary = "Overlay image onto a PDF file",
|
||||||
@@ -41,7 +50,9 @@ public class OverlayImageController {
|
|||||||
try {
|
try {
|
||||||
byte[] pdfBytes = pdfFile.getBytes();
|
byte[] pdfBytes = pdfFile.getBytes();
|
||||||
byte[] imageBytes = imageFile.getBytes();
|
byte[] imageBytes = imageFile.getBytes();
|
||||||
byte[] result = PdfUtils.overlayImage(pdfBytes, imageBytes, x, y, everyPage);
|
byte[] result =
|
||||||
|
PdfUtils.overlayImage(
|
||||||
|
pdfDocumentFactory, pdfBytes, imageBytes, x, y, everyPage);
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
result,
|
result,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
@@ -13,6 +12,7 @@ import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
|||||||
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -26,6 +26,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
|
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -36,6 +37,13 @@ public class PageNumbersController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PageNumbersController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add page numbers to a PDF document",
|
summary = "Add page numbers to a PDF document",
|
||||||
@@ -52,7 +60,7 @@ public class PageNumbersController {
|
|||||||
String customText = request.getCustomText();
|
String customText = request.getCustomText();
|
||||||
int pageNumber = startingNumber;
|
int pageNumber = startingNumber;
|
||||||
byte[] fileBytes = file.getBytes();
|
byte[] fileBytes = file.getBytes();
|
||||||
PDDocument document = Loader.loadPDF(fileBytes);
|
PDDocument document = pdfDocumentFactory.load(fileBytes);
|
||||||
float font_size = request.getFontSize();
|
float font_size = request.getFontSize();
|
||||||
String font_type = request.getFontType();
|
String font_type = request.getFontType();
|
||||||
float marginFactor;
|
float marginFactor;
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.PrintFileRequest;
|
import stirling.software.SPDF.model.api.misc.PrintFileRequest;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
|
@Slf4j
|
||||||
public class PrintFileController {
|
public class PrintFileController {
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@@ -59,7 +61,7 @@ public class PrintFileController {
|
|||||||
new IllegalArgumentException(
|
new IllegalArgumentException(
|
||||||
"No matching printer found"));
|
"No matching printer found"));
|
||||||
|
|
||||||
System.out.println("Selected Printer: " + selectedService.getName());
|
log.info("Selected Printer: " + selectedService.getName());
|
||||||
|
|
||||||
if ("application/pdf".equals(contentType)) {
|
if ("application/pdf".equals(contentType)) {
|
||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -20,6 +21,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@@ -31,6 +33,13 @@ public class RepairController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public RepairController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/repair")
|
@PostMapping(consumes = "multipart/form-data", value = "/repair")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Repair a PDF file",
|
summary = "Repair a PDF file",
|
||||||
@@ -58,7 +67,7 @@ public class RepairController {
|
|||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the optimized PDF file
|
// Read the optimized PDF file
|
||||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
pdfBytes = pdfDocumentFactory.loadToBytes(tempOutputFile.toFile());
|
||||||
|
|
||||||
// Return the optimized PDF as a response
|
// Return the optimized PDF as a response
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -75,7 +76,8 @@ public class ShowJavascript {
|
|||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
script.getBytes(StandardCharsets.UTF_8),
|
script.getBytes(StandardCharsets.UTF_8),
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js");
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js",
|
||||||
|
MediaType.TEXT_PLAIN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import java.util.List;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
@@ -25,6 +24,7 @@ import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
|||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
@@ -38,6 +38,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.misc.AddStampRequest;
|
import stirling.software.SPDF.model.api.misc.AddStampRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -45,6 +46,13 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class StampController {
|
public class StampController {
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public StampController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add stamp to a PDF file",
|
summary = "Add stamp to a PDF file",
|
||||||
@@ -86,7 +94,7 @@ public class StampController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the input PDF
|
// Load the input PDF
|
||||||
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
||||||
|
|
||||||
List<Integer> pageNumbers = request.getPageNumbersList(document, true);
|
List<Integer> pageNumbers = request.getPageNumbersList(document, true);
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,6 @@ package stirling.software.SPDF.controller.api.pipeline;
|
|||||||
|
|
||||||
public interface UserServiceInterface {
|
public interface UserServiceInterface {
|
||||||
String getApiKeyForUser(String username);
|
String getApiKeyForUser(String username);
|
||||||
|
|
||||||
|
String getCurrentUsername();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import java.security.cert.CertificateException;
|
|||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.examples.signature.CreateSignatureBase;
|
import org.apache.pdfbox.examples.signature.CreateSignatureBase;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
||||||
@@ -35,6 +34,7 @@ import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
|||||||
import org.bouncycastle.pkcs.PKCSException;
|
import org.bouncycastle.pkcs.PKCSException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -47,6 +47,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -71,6 +72,13 @@ public class CertSignController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CertSignController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Sign PDF with a Digital Certificate",
|
summary = "Sign PDF with a Digital Certificate",
|
||||||
@@ -122,7 +130,7 @@ public class CertSignController {
|
|||||||
|
|
||||||
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
|
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
sign(pdf.getBytes(), baos, createSignature, name, location, reason);
|
sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, name, location, reason);
|
||||||
return WebResponseUtils.boasToWebResponse(
|
return WebResponseUtils.boasToWebResponse(
|
||||||
baos,
|
baos,
|
||||||
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
||||||
@@ -130,13 +138,14 @@ public class CertSignController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void sign(
|
private static void sign(
|
||||||
|
CustomPDDocumentFactory pdfDocumentFactory,
|
||||||
byte[] input,
|
byte[] input,
|
||||||
OutputStream output,
|
OutputStream output,
|
||||||
CreateSignature instance,
|
CreateSignature instance,
|
||||||
String name,
|
String name,
|
||||||
String location,
|
String location,
|
||||||
String reason) {
|
String reason) {
|
||||||
try (PDDocument doc = Loader.loadPDF(input)) {
|
try (PDDocument doc = pdfDocumentFactory.load(input)) {
|
||||||
PDSignature signature = new PDSignature();
|
PDSignature signature = new PDSignature();
|
||||||
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
||||||
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
|
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package stirling.software.SPDF.controller.api.security;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
||||||
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import stirling.software.SPDF.model.api.security.AddPasswordRequest;
|
import stirling.software.SPDF.model.api.security.AddPasswordRequest;
|
||||||
import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
|
import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -30,6 +31,13 @@ public class PasswordController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PasswordController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove password from a PDF file",
|
summary = "Remove password from a PDF file",
|
||||||
@@ -39,8 +47,7 @@ public class PasswordController {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
String password = request.getPassword();
|
String password = request.getPassword();
|
||||||
|
PDDocument document = pdfDocumentFactory.load(fileInput, password);
|
||||||
PDDocument document = Loader.loadPDF(fileInput.getBytes(), password);
|
|
||||||
document.setAllSecurityToBeRemoved(true);
|
document.setAllSecurityToBeRemoved(true);
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
@@ -69,7 +76,7 @@ public class PasswordController {
|
|||||||
boolean canPrint = request.isCanPrint();
|
boolean canPrint = request.isCanPrint();
|
||||||
boolean canPrintFaithful = request.isCanPrintFaithful();
|
boolean canPrintFaithful = request.isCanPrintFaithful();
|
||||||
|
|
||||||
PDDocument document = Loader.loadPDF(fileInput.getBytes());
|
PDDocument document = pdfDocumentFactory.load(fileInput);
|
||||||
AccessPermission ap = new AccessPermission();
|
AccessPermission ap = new AccessPermission();
|
||||||
ap.setCanAssembleDocument(!canAssembleDocument);
|
ap.setCanAssembleDocument(!canAssembleDocument);
|
||||||
ap.setCanExtractContent(!canExtractContent);
|
ap.setCanExtractContent(!canExtractContent);
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import stirling.software.SPDF.model.PDFText;
|
import stirling.software.SPDF.model.PDFText;
|
||||||
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
||||||
import stirling.software.SPDF.pdf.TextFinder;
|
import stirling.software.SPDF.pdf.TextFinder;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -35,6 +36,13 @@ public class RedactController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RedactController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RedactController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public RedactController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/auto-redact", consumes = "multipart/form-data")
|
@PostMapping(value = "/auto-redact", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Redacts listOfText in a PDF document",
|
summary = "Redacts listOfText in a PDF document",
|
||||||
@@ -50,10 +58,8 @@ public class RedactController {
|
|||||||
float customPadding = request.getCustomPadding();
|
float customPadding = request.getCustomPadding();
|
||||||
boolean convertPDFToImage = request.isConvertPDFToImage();
|
boolean convertPDFToImage = request.isConvertPDFToImage();
|
||||||
|
|
||||||
System.out.println(listOfTextString);
|
|
||||||
String[] listOfText = listOfTextString.split("\n");
|
String[] listOfText = listOfTextString.split("\n");
|
||||||
byte[] bytes = file.getBytes();
|
PDDocument document = pdfDocumentFactory.load(file);
|
||||||
PDDocument document = Loader.loadPDF(bytes);
|
|
||||||
|
|
||||||
Color redactColor;
|
Color redactColor;
|
||||||
try {
|
try {
|
||||||
@@ -68,7 +74,6 @@ public class RedactController {
|
|||||||
|
|
||||||
for (String text : listOfText) {
|
for (String text : listOfText) {
|
||||||
text = text.trim();
|
text = text.trim();
|
||||||
System.out.println(text);
|
|
||||||
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
||||||
List<PDFText> foundTexts = textFinder.getTextLocations(document);
|
List<PDFText> foundTexts = textFinder.getTextLocations(document);
|
||||||
redactFoundText(document, foundTexts, customPadding, redactColor);
|
redactFoundText(document, foundTexts, customPadding, redactColor);
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
@@ -12,6 +10,7 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
|||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -24,6 +23,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -33,6 +33,13 @@ public class RemoveCertSignController {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RemoveCertSignController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RemoveCertSignController.class);
|
||||||
|
|
||||||
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public RemoveCertSignController(CustomPDDocumentFactory pdfDocumentFactory) {
|
||||||
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-cert-sign")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-cert-sign")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove digital signature from PDF",
|
summary = "Remove digital signature from PDF",
|
||||||
@@ -42,14 +49,8 @@ public class RemoveCertSignController {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile pdf = request.getFileInput();
|
MultipartFile pdf = request.getFileInput();
|
||||||
|
|
||||||
// Convert MultipartFile to byte[]
|
|
||||||
byte[] pdfBytes = pdf.getBytes();
|
|
||||||
|
|
||||||
// Create a ByteArrayOutputStream to hold the resulting PDF
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
// Load the PDF document
|
// Load the PDF document
|
||||||
PDDocument document = Loader.loadPDF(pdfBytes);
|
PDDocument document = pdfDocumentFactory.load(pdf);
|
||||||
|
|
||||||
// Get the document catalog
|
// Get the document catalog
|
||||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||||
@@ -67,14 +68,9 @@ public class RemoveCertSignController {
|
|||||||
acroForm.flatten(fieldsToRemove, false);
|
acroForm.flatten(fieldsToRemove, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the modified document to the ByteArrayOutputStream
|
|
||||||
document.save(baos);
|
|
||||||
document.close();
|
|
||||||
|
|
||||||
// Return the modified PDF as a response
|
// Return the modified PDF as a response
|
||||||
return WebResponseUtils.boasToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
baos,
|
document,
|
||||||
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
|
||||||
+ "_unsigned.pdf");
|
+ "_unsigned.pdf");
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user