Compare commits
175 Commits
add-elemen
...
v0.20.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
571320b9ba | ||
|
|
07e9fcb6cc | ||
|
|
7a8719743d | ||
|
|
746f2d0949 | ||
|
|
3986858adb | ||
|
|
589cb8d91f | ||
|
|
705c75e51d | ||
|
|
6acb593411 | ||
|
|
8060451713 | ||
|
|
6130f14d5a | ||
|
|
0fbc461877 | ||
|
|
89e461e4f6 | ||
|
|
ba4ad1aff9 | ||
|
|
be1904749b | ||
|
|
166fa0eb87 | ||
|
|
46032b8ebb | ||
|
|
4a6bd60466 | ||
|
|
f85c8ea5ec | ||
|
|
06ef09035d | ||
|
|
13301e4606 | ||
|
|
b59651a0fb | ||
|
|
86b8d7f804 | ||
|
|
e4dded3faa | ||
|
|
75cf3ed0c1 | ||
|
|
2fa68be36b | ||
|
|
d09b252a4a | ||
|
|
a5165b04cd | ||
|
|
1bd17eded6 | ||
|
|
18d289d3b7 | ||
|
|
e43e6d18b9 | ||
|
|
c807d20590 | ||
|
|
686af16cf5 | ||
|
|
219dd7834f | ||
|
|
1b83fda349 | ||
|
|
490acddc65 | ||
|
|
e69ed06b4f | ||
|
|
2fe9b5a24b | ||
|
|
3912f42128 | ||
|
|
801e307005 | ||
|
|
c8acddb251 | ||
|
|
d8cf7e81b9 | ||
|
|
c4ad442ec3 | ||
|
|
c8e5023ec1 | ||
|
|
5281d7a49a | ||
|
|
77dcf04cfe | ||
|
|
b6523e9989 | ||
|
|
d52a00185b | ||
|
|
00487275a7 | ||
|
|
823c8eb53e | ||
|
|
e9b8981a35 | ||
|
|
575e0b3e54 | ||
|
|
db931717a1 | ||
|
|
787c59efd3 | ||
|
|
45aead89e3 | ||
|
|
81d49b722b | ||
|
|
ab9e7bbb8c | ||
|
|
ee223d0405 | ||
|
|
99050ad73e | ||
|
|
9fc873e973 | ||
|
|
52fe4c6aa6 | ||
|
|
66df7053bb | ||
|
|
edde1a6436 | ||
|
|
50ee829e5f | ||
|
|
1924dfb4f1 | ||
|
|
873a4ecb7e | ||
|
|
32da14acbf | ||
|
|
139c793b5e | ||
|
|
e717d83f75 | ||
|
|
ef12c2f892 | ||
|
|
362a7ff434 | ||
|
|
624e015315 | ||
|
|
d76752d7f6 | ||
|
|
7260e578e3 | ||
|
|
2544b762ba | ||
|
|
164d1abdbb | ||
|
|
863b48b5a9 | ||
|
|
b5e0e147ac | ||
|
|
2fe454ea47 | ||
|
|
c8458ffe50 | ||
|
|
572f9f728f | ||
|
|
6c963e1b6c | ||
|
|
770b61bb7a | ||
|
|
db64b3f71d | ||
|
|
5fad085db5 | ||
|
|
7ed8a69326 | ||
|
|
5d1786cda0 | ||
|
|
550f8b0eea | ||
|
|
b5d5b6e3e2 | ||
|
|
97b6f0eeb4 | ||
|
|
bd7e2fea0b | ||
|
|
eee7e4d707 | ||
|
|
43410de851 | ||
|
|
37c92ee9aa | ||
|
|
351cf25f86 | ||
|
|
10cb02020c | ||
|
|
5e40f00bae | ||
|
|
cebc0daf2b | ||
|
|
04f3f735fc | ||
|
|
f7ef8c32aa | ||
|
|
49f2071a93 | ||
|
|
ecb62e0c94 | ||
|
|
846ebe6dda | ||
|
|
56afd35c82 | ||
|
|
eadd513b02 | ||
|
|
97f581ad6d | ||
|
|
d96a3db60a | ||
|
|
0592bac5bf | ||
|
|
4b0df4ffd5 | ||
|
|
a244d563f2 | ||
|
|
f433e8032f | ||
|
|
23b85dc47c | ||
|
|
6a9ef7d538 | ||
|
|
c75efede79 | ||
|
|
a0212bbfb7 | ||
|
|
87efa175cb | ||
|
|
ad7150d616 | ||
|
|
6fe268adcb | ||
|
|
0c2b05eabf | ||
|
|
38ebc28108 | ||
|
|
0a08831aac | ||
|
|
2a744473f9 | ||
|
|
56ce53a966 | ||
|
|
6baf1f94c1 | ||
|
|
8a57165547 | ||
|
|
de9e9a0f84 | ||
|
|
73a55c0666 | ||
|
|
4ac5262be2 | ||
|
|
dfee149da0 | ||
|
|
fbe0a8ddcc | ||
|
|
56a1867270 | ||
|
|
c23a5ad5fb | ||
|
|
31fbeaae1d | ||
|
|
e0d79990c8 | ||
|
|
468808167c | ||
|
|
5af5794dfe | ||
|
|
1d470691a5 | ||
|
|
f32832f70d | ||
|
|
cd0464092a | ||
|
|
c67eaf2b4d | ||
|
|
b1f80bc9f6 | ||
|
|
f3742ebeb6 | ||
|
|
adc7b9606b | ||
|
|
aa34257080 | ||
|
|
0f126eaf81 | ||
|
|
7389543af6 | ||
|
|
9795c68220 | ||
|
|
7ffa447cbc | ||
|
|
d755fd1861 | ||
|
|
e273294360 | ||
|
|
827ed62761 | ||
|
|
ee96d2a0e3 | ||
|
|
044a779a7c | ||
|
|
03a8f45128 | ||
|
|
b5423f3434 | ||
|
|
88c993367f | ||
|
|
04acdb3b02 | ||
|
|
cd3cc15888 | ||
|
|
76e6a23674 | ||
|
|
4fbfd0bae4 | ||
|
|
b74819cf6c | ||
|
|
a5ad9e13fe | ||
|
|
328e873344 | ||
|
|
39045df785 | ||
|
|
d83bd1ae94 | ||
|
|
143b770882 | ||
|
|
d91c600925 | ||
|
|
cbac784c57 | ||
|
|
f535387ac4 | ||
|
|
739dcc1327 | ||
|
|
3864e130cc | ||
|
|
5b0145fa47 | ||
|
|
7dfeb4bb0f | ||
|
|
eda91cc556 | ||
|
|
c853465d1d | ||
|
|
6ca9001fe6 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
# Ignore all JavaScript files in a directory
|
# Ignore all JavaScript files in a directory
|
||||||
src/main/resources/static/pdfjs/* linguist-vendored
|
src/main/resources/static/pdfjs/* linguist-vendored
|
||||||
src/main/resources/static/pdfjs/** linguist-vendored
|
src/main/resources/static/pdfjs/** linguist-vendored
|
||||||
|
|||||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -9,3 +9,7 @@ updates:
|
|||||||
directory: "/" # Location of package manifests
|
directory: "/" # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/" # Location of Dockerfile
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|||||||
48
.github/workflows/licenses-update.yml
vendored
Normal file
48
.github/workflows/licenses-update.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: License Report Workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'build.gradle'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate-license-report:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'adopt'
|
||||||
|
|
||||||
|
- name: Run Gradle Command
|
||||||
|
run: ./gradlew clean generateLicenseReport
|
||||||
|
|
||||||
|
- name: Move and Rename License File
|
||||||
|
run: |
|
||||||
|
mv build/reports/dependency-license/index.json src/main/resources/static/3rdPartyLicenses.json
|
||||||
|
|
||||||
|
- name: Check for Changes
|
||||||
|
id: git-check
|
||||||
|
run: |
|
||||||
|
git add src/main/resources/static/3rdPartyLicenses.json
|
||||||
|
git diff --staged --exit-code || echo "changes=true" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Commit and Push Changes
|
||||||
|
if: env.changes == 'true'
|
||||||
|
run: |
|
||||||
|
git config --global user.name 'Stirling-PDF-Bot'
|
||||||
|
git config --global user.email 'Stirling-PDF-Bot@stirlingtools.com'
|
||||||
|
git commit -m "Update 3rd Party Licenses"
|
||||||
|
git push
|
||||||
|
|
||||||
3
.github/workflows/push-docker.yml
vendored
3
.github/workflows/push-docker.yml
vendored
@@ -6,6 +6,9 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- main
|
- main
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
4
.github/workflows/releaseArtifacts.yml
vendored
4
.github/workflows/releaseArtifacts.yml
vendored
@@ -3,7 +3,9 @@ name: Release Artifacts
|
|||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
56
.github/workflows/test.yml
vendored
Normal file
56
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: Docker Compose Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- '**.gradle'
|
||||||
|
- '!src/main/java/resources/messages*'
|
||||||
|
- 'exampleYmlFiles/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'Dockerfile**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Java 17
|
||||||
|
uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'adopt'
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Run Docker Compose Tests
|
||||||
|
run: |
|
||||||
|
chmod +x ./gradlew
|
||||||
|
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
|
||||||
|
|
||||||
|
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache
|
||||||
|
key: ${{ runner.os }}-buildx-${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-buildx-
|
||||||
|
|
||||||
|
- name: Install Docker Compose
|
||||||
|
run: |
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
|
||||||
|
|
||||||
|
- name: Run Docker Compose Tests
|
||||||
|
run: |
|
||||||
|
chmod +x ./test.sh
|
||||||
|
./test.sh
|
||||||
40
CONTRIBUTING.md
Normal file
40
CONTRIBUTING.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Contributing to Stirling-PDF
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to Stirling-PDF! There are many ways to contribute other than writing code. For example, reporting bugs, creating suggestions, and adding or modifying translations.
|
||||||
|
|
||||||
|
## Issue Guidelines
|
||||||
|
|
||||||
|
Issues can be used to report bugs, request features, or ask questions. If you have a question, you could also ask us in our [Discord](https://discord.gg/FJUSXUSYec).
|
||||||
|
|
||||||
|
Before opening an issue, please check to make sure someone hasn't already opened an issue about it.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
Before you start working on an issue, please comment on (or create) the issue and wait for it to be assigned to you. If someone has already been assigned but didn't have the time to work on it lately, please communicate with them and ask if they're still working on it. This is to avoid multiple people working on the same issue.
|
||||||
|
|
||||||
|
Once you have been assigned an issue, you can start working on it. When you are ready to submit your changes, open a pull request.
|
||||||
|
For a detailed pull request tutorial, see [this guide](https://www.digitalocean.com/community/tutorials/how-to-create-a-pull-request-on-github).
|
||||||
|
|
||||||
|
Please make sure your Pull Request adheres to the following guidelines:
|
||||||
|
|
||||||
|
- Use the PR template provided.
|
||||||
|
- Keep your Pull Request title succinct, detailed and to the point.
|
||||||
|
- Keep commits atomic. One commit should contain one change. If you want to make multiple changes, submit multiple Pull Requests.
|
||||||
|
- Commits should be clear, concise and easy to understand.
|
||||||
|
- References to the Issue number in the Pull Request and/or Commit message.
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
If you would like to add or modify a translation, please see [How to add new languages to Stirling-PDF](HowToAddNewLanguage.md). Also, please create a Pull Request so others can use it!
|
||||||
|
|
||||||
|
## Fixing Bugs or Adding a New Feature
|
||||||
|
|
||||||
|
First, make sure you've read the section [Pull Requests](#pull-requests).
|
||||||
|
|
||||||
|
To build from source, please follow this [Guide](LocalRunGuide.md).
|
||||||
|
|
||||||
|
If, at any point of time, you have a question, please feel free to ask in the same issue thread or in our [Discord](https://discord.gg/FJUSXUSYec).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing to this project, you agree that your contributions will be licensed under the [GPL 3 License](LICENSE). You also acknowledge and agree that your contributions will be included in Stirling-PDF and that they can be relicensed in the future under the MPL 2.0 (Mozilla Public License Version 2.0) license.
|
||||||
@@ -6,7 +6,8 @@ FROM ubuntu:latest AS base
|
|||||||
# JDK for app
|
# JDK for app
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
openjdk-17-jre
|
openjdk-17-jre && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Doc conversion
|
# Doc conversion
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
@@ -18,7 +19,8 @@ RUN apt-get update && \
|
|||||||
libreoffice-impress \
|
libreoffice-impress \
|
||||||
python3-uno \
|
python3-uno \
|
||||||
curl \
|
curl \
|
||||||
unoconv
|
unoconv && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
||||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||||
@@ -30,21 +32,12 @@ apt-get update && \
|
|||||||
python3-pip \
|
python3-pip \
|
||||||
ocrmypdf \
|
ocrmypdf \
|
||||||
unpaper && \
|
unpaper && \
|
||||||
pip install --upgrade pip && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
mv /usr/share/tesseract-ocr /usr/share/tesseract-ocr-original && \
|
||||||
|
pip install --no-cache-dir --upgrade pip && \
|
||||||
pip install --no-cache-dir --upgrade ocrmypdf && \
|
pip install --no-cache-dir --upgrade ocrmypdf && \
|
||||||
pip install --no-cache-dir --upgrade pillow==10.0.1 reportlab==3.6.13 wheel==0.38.1 setuptools==65.5.1 pyjwt==2.4.0 cryptography==39.0.1
|
pip install --no-cache-dir --upgrade pillow==10.0.1 reportlab==3.6.13 wheel==0.38.1 setuptools==65.5.1 pyjwt==2.4.0 cryptography==39.0.1
|
||||||
|
|
||||||
|
|
||||||
#CV and HTML
|
#CV and HTML
|
||||||
RUN pip install --no-cache-dir opencv-python-headless WeasyPrint
|
RUN pip install --no-cache-dir opencv-python-headless WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
# cleanup and etc
|
|
||||||
RUN rm -rf /var/lib/apt/lists/* && \
|
|
||||||
mkdir /usr/share/tesseract-ocr-original && \
|
|
||||||
cp -r /usr/share/tesseract-ocr/* /usr/share/tesseract-ocr-original && \
|
|
||||||
rm -rf /usr/share/tesseract-ocr
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
41
FolderScanning.md
Normal file
41
FolderScanning.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
## User Guide for Local Directory Scanning and File Processing
|
||||||
|
|
||||||
|
### Whilst Pipelines are in alpha...
|
||||||
|
You must enable this alpha functionality by setting
|
||||||
|
```yaml
|
||||||
|
system:
|
||||||
|
enableAlphaFunctionality: true
|
||||||
|
```
|
||||||
|
To true like in the above for your `/config/settings.yml` file, after restarting Stirling-PDF you should see both UI and folder scanning enabled.
|
||||||
|
|
||||||
|
### Setting Up Watched Folders:
|
||||||
|
- Create a folder where you want your files to be monitored. This is your 'watched folder'.
|
||||||
|
- The default directory for this is `./pipeline/watchedFolders/`
|
||||||
|
- Place any directories you want to be scanned into this folder, this folder should contain multiple folders each for their own tasks and pipelines.
|
||||||
|
|
||||||
|
### Configuring Processing with JSON Files:
|
||||||
|
- In each directory you want processed (e.g `./pipeline/watchedFolders/officePrinter`), include a JSON configuration file.
|
||||||
|
- This JSON file should specify how you want the files in the directory to be handled (e.g., what operations to perform on them) which can be made, configured and downloaded from Stirling-PDF Pipeline interface.r
|
||||||
|
|
||||||
|
### Automatic Scanning and Processing:
|
||||||
|
- The system automatically checks the watched folder every minute for new directories and files to process.
|
||||||
|
- When a directory with a valid JSON configuration file is found, it begins processing the files inside as per the configuration.
|
||||||
|
|
||||||
|
### Processing Steps:
|
||||||
|
- Files in each directory are processed according to the instructions in the JSON file.
|
||||||
|
- This might involve file conversions, data filtering, renaming files, etc. If the output of a step is a zip, this zip will be automatically unzipped as it passes to next process.
|
||||||
|
|
||||||
|
### Results and Output:
|
||||||
|
- After processing, the results are saved in a specified output location. This could be a different folder or location as defined in the JSON file or the default location `./pipeline/finishedFolders/`.
|
||||||
|
- Each processed file is named and organized according to the rules set in the JSON configuration.
|
||||||
|
|
||||||
|
### Completion and Cleanup:
|
||||||
|
- Once processing is complete, the original files in the watched folder's directory are removed.
|
||||||
|
- You can find the processed files in the designated output location.
|
||||||
|
|
||||||
|
### Error Handling:
|
||||||
|
- If there's an error during processing, the system will not delete the original files, allowing you to check and retry if necessary.
|
||||||
|
|
||||||
|
### User Interaction:
|
||||||
|
- As a user, your main tasks are to set up the watched folders, place directories with files for processing, and create the corresponding JSON configuration files.
|
||||||
|
- The system handles the rest, including scanning, processing, and outputting results.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<p align="center"><img src="https://raw.githubusercontent.com/Frooodle/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
|
<p align="center"><img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
@@ -8,15 +8,15 @@ Fork Stirling-PDF and make a new branch out of Main
|
|||||||
|
|
||||||
Then add reference to the language in the navbar by adding a new language entry to the dropdown
|
Then add reference to the language in the navbar by adding a new language entry to the dropdown
|
||||||
|
|
||||||
https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html
|
https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html
|
||||||
and add a flag svg file to
|
and add a flag svg file to
|
||||||
https://github.com/Frooodle/Stirling-PDF/tree/main/src/main/resources/static/images/flags
|
https://github.com/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags
|
||||||
Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/)
|
Any SVG flags are fine, i got most of mine from [here](https://flagicons.lipis.dev/)
|
||||||
If your language isnt represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia
|
If your language isnt represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia
|
||||||
|
|
||||||
|
|
||||||
For example to add Polish you would add
|
For example to add Polish you would add
|
||||||
```
|
```html
|
||||||
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">
|
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">
|
||||||
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
|
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
|
||||||
</a>
|
</a>
|
||||||
@@ -25,7 +25,7 @@ The data-language-code is the code used to reference the file in the next step.
|
|||||||
|
|
||||||
Start by copying the existing english property file
|
Start by copying the existing english property file
|
||||||
|
|
||||||
[https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties](https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)
|
[https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)
|
||||||
|
|
||||||
Copy and rename it to messages_{your data-language-code here}.properties, in the polish example you would set the name to messages_pl_PL.properties
|
Copy and rename it to messages_{your data-language-code here}.properties, in the polish example you would set the name to messages_pl_PL.properties
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/.git &&\
|
cd ~/.git &&\
|
||||||
git clone https://github.com/Frooodle/Stirling-PDF.git &&\
|
git clone https://github.com/Stirling-Tools/Stirling-PDF.git &&\
|
||||||
cd Stirling-PDF &&\
|
cd Stirling-PDF &&\
|
||||||
chmod +x ./gradlew &&\
|
chmod +x ./gradlew &&\
|
||||||
./gradlew build
|
./gradlew build
|
||||||
|
|||||||
42
PipelineFeature.md
Normal file
42
PipelineFeature.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Pipeline Configuration and Usage Tutorial
|
||||||
|
|
||||||
|
## Whilst Pipelines are in alpha...
|
||||||
|
You must enable this alpha functionality by setting
|
||||||
|
```yaml
|
||||||
|
system:
|
||||||
|
enableAlphaFunctionality: true
|
||||||
|
```
|
||||||
|
To true like in the above for your `/config/settings.yml` file, after restarting Stirling-PDF you should see both UI and folder scanning enabled.
|
||||||
|
|
||||||
|
|
||||||
|
## Steps to Configure and Use Your Pipeline
|
||||||
|
|
||||||
|
1. **Access Configuration**
|
||||||
|
- Upon entering the screen, click on the **Configure** button.
|
||||||
|
|
||||||
|
2. **Enter Pipeline Name**
|
||||||
|
- Provide a name for your pipeline in the designated field.
|
||||||
|
|
||||||
|
3. **Select Operations**
|
||||||
|
- Choose the operations for your pipeline (e.g., **Split Pages**), then click **Add Operation**.
|
||||||
|
|
||||||
|
4. **Configure Operation Settings**
|
||||||
|
- Input the necessary settings for each added operation. Settings are highlighted in yellow if customization is needed.
|
||||||
|
|
||||||
|
5. **Add More Operations**
|
||||||
|
- You can add and adjust the order of multiple operations. Ensure each operation's settings are customized.
|
||||||
|
|
||||||
|
6. **Save Settings**
|
||||||
|
- Click **Save Operation Settings** after customizing settings for each operation.
|
||||||
|
|
||||||
|
7. **Validate Pipeline**
|
||||||
|
- Use the **Validation** button to check your pipeline. A green indicator signifies correct setup; a pop-out error indicates issues.
|
||||||
|
|
||||||
|
8. **Download Pipeline Configuration**
|
||||||
|
- To use the configuration for folder scanning (or save it for future use and reupload it), you can also download a JSON file in this menu. You can also pre-load this for future use by placing it in ``/pipeline/defaultWebUIConfigs/``. It will then appear in the dropdown menu for all users to use.
|
||||||
|
|
||||||
|
9. **Submit Files for Processing**
|
||||||
|
- If your pipeline is correctly set up close the configure menu, input the files and hit **Submit**.
|
||||||
|
|
||||||
|
10. **Note on Web UI Limitations**
|
||||||
|
- The current web UI version does not support operations that require multiple different types of inputs, such as adding a separate image to a PDF.
|
||||||
161
README.md
161
README.md
@@ -1,14 +1,14 @@
|
|||||||
<p align="center"><img src="https://raw.githubusercontent.com/Frooodle/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
|
<p align="center"><img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png" width="80" ><br><h1 align="center">Stirling-PDF</h1>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://hub.docker.com/r/frooodle/s-pdf)
|
[](https://hub.docker.com/r/frooodle/s-pdf)
|
||||||
[](https://discord.gg/Cn8pWhQRxZ)
|
[](https://discord.gg/Cn8pWhQRxZ)
|
||||||
[](https://github.com/Frooodle/Stirling-PDF/)
|
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
||||||
[](https://github.com/Frooodle/stirling-pdf)
|
[](https://github.com/Stirling-Tools/stirling-pdf)
|
||||||
[](https://www.paypal.com/paypalme/froodleplex)
|
[](https://www.paypal.com/paypalme/froodleplex)
|
||||||
[](https://github.com/sponsors/Frooodle)
|
[](https://github.com/sponsors/Frooodle)
|
||||||
|
|
||||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Frooodle/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
|
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
|
||||||
|
|
||||||
This is a powerful locally hosted web based PDF manipulation tool using docker that allows you to perform various operations on PDF files, such as splitting merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application started as a 100% ChatGPT-made application and has evolved to include a wide range of features to handle all your PDF needs.
|
This is a powerful locally hosted web based PDF manipulation tool using docker that allows you to perform various operations on PDF files, such as splitting merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application started as a 100% ChatGPT-made application and has evolved to include a wide range of features to handle all your PDF needs.
|
||||||
|
|
||||||
@@ -16,100 +16,98 @@ Stirling PDF makes no outbound calls for any record keeping or tracking.
|
|||||||
|
|
||||||
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
|
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
|
||||||
|
|
||||||
Please feel free to submit feature requests or report bugs either through GitHub issues or on our [Discord](https://discord.gg/Cn8pWhQRxZ)
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Dark mode support.
|
- Dark mode support.
|
||||||
- Custom download options (see [here](https://github.com/Frooodle/Stirling-PDF/blob/main/images/settings.png) for example)
|
- Custom download options (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/images/settings.png) for example)
|
||||||
- Parallel file processing and downloads
|
- Parallel file processing and downloads
|
||||||
- API for integration with external scripts
|
- API for integration with external scripts
|
||||||
- Optional Login and Authentication support (see [here](https://github.com/Frooodle/Stirling-PDF/tree/main#login-authentication) for documentation)
|
- Optional Login and Authentication support (see [here](https://github.com/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation)
|
||||||
|
|
||||||
|
|
||||||
## **PDF Features**
|
## **PDF Features**
|
||||||
|
|
||||||
### **Page Operations**
|
### **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)
|
- 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.
|
- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages.
|
||||||
- Merge multiple PDFs together into a single resultant file.
|
- Merge multiple PDFs together into a single resultant file.
|
||||||
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
|
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
|
||||||
- Reorganize PDF pages into different orders.
|
- Reorganize PDF pages into different orders.
|
||||||
- Rotate PDFs in 90-degree increments.
|
- Rotate PDFs in 90-degree increments.
|
||||||
- Remove pages.
|
- Remove pages.
|
||||||
- Multi-page layout (Format PDFs into a multi-paged page).
|
- Multi-page layout (Format PDFs into a multi-paged page).
|
||||||
- Scale page contents size by set %.
|
- Scale page contents size by set %.
|
||||||
- Adjust Contrast.
|
- Adjust Contrast.
|
||||||
- Crop PDF.
|
- Crop PDF.
|
||||||
- Auto Split PDF (With physically scanned page dividers).
|
- Auto Split PDF (With physically scanned page dividers).
|
||||||
- Extract page(s).
|
- Extract page(s).
|
||||||
- Convert PDF to a single page.
|
- Convert PDF to a single page.
|
||||||
|
|
||||||
### **Conversion Operations**
|
### **Conversion Operations**
|
||||||
- Convert PDFs to and from images.
|
- Convert PDFs to and from images.
|
||||||
- Convert any common file to PDF (using LibreOffice).
|
- Convert any common file to PDF (using LibreOffice).
|
||||||
- Convert PDF to Word/Powerpoint/Others (using LibreOffice).
|
- Convert PDF to Word/Powerpoint/Others (using LibreOffice).
|
||||||
- Convert HTML to PDF.
|
- Convert HTML to PDF.
|
||||||
- URL to PDF.
|
- URL to PDF.
|
||||||
- Markdown to PDF.
|
- Markdown to PDF.
|
||||||
|
|
||||||
### **Security & Permissions**
|
### **Security & Permissions**
|
||||||
- Add and remove passwords.
|
- Add and remove passwords.
|
||||||
- Change/set PDF Permissions.
|
- Change/set PDF Permissions.
|
||||||
- Add watermark(s).
|
- Add watermark(s).
|
||||||
- Certify/sign PDFs.
|
- Certify/sign PDFs.
|
||||||
- Sanitize PDFs.
|
- Sanitize PDFs.
|
||||||
- Auto-redact text.
|
- Auto-redact text.
|
||||||
|
|
||||||
### **Other Operations**
|
### **Other Operations**
|
||||||
- Add/Generate/Write signatures.
|
- Add/Generate/Write signatures.
|
||||||
- Repair PDFs.
|
- Repair PDFs.
|
||||||
- Detect and remove blank pages.
|
- Detect and remove blank pages.
|
||||||
- Compare 2 PDFs and show differences in text.
|
- Compare 2 PDFs and show differences in text.
|
||||||
- Add images to PDFs.
|
- Add images to PDFs.
|
||||||
- Compress PDFs to decrease their filesize (Using OCRMyPDF).
|
- Compress PDFs to decrease their filesize (Using OCRMyPDF).
|
||||||
- Extract images from PDF.
|
- Extract images from PDF.
|
||||||
- Extract images from Scans.
|
- Extract images from Scans.
|
||||||
- Add page numbers.
|
- Add page numbers.
|
||||||
- Auto rename file by detecting PDF header text.
|
- Auto rename file by detecting PDF header text.
|
||||||
- OCR on PDF (Using OCRMyPDF).
|
- OCR on PDF (Using OCRMyPDF).
|
||||||
- PDF/A conversion (Using OCRMyPDF).
|
- PDF/A conversion (Using OCRMyPDF).
|
||||||
- Edit metadata.
|
- Edit metadata.
|
||||||
- Flatten PDFs.
|
- Flatten PDFs.
|
||||||
- Get all information on a PDF to view or export as JSON.
|
- Get all information on a PDF to view or export as JSON.
|
||||||
|
|
||||||
|
|
||||||
For a overview of the tasks and the technology each uses please view [Endpoint-groups.md](https://github.com/Frooodle/Stirling-PDF/blob/main/Endpoint-groups.md)
|
For a overview of the tasks and the technology each uses please view [Endpoint-groups.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
|
||||||
Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) hosted by the team at adminforge.de
|
Demo of the app is available [here](https://stirlingpdf.io). username: demo, password: demo
|
||||||
|
|
||||||
## Technologies used
|
## Technologies used
|
||||||
- Spring Boot + Thymeleaf
|
- Spring Boot + Thymeleaf
|
||||||
- PDFBox
|
- [PDFBox](https://github.com/apache/pdfbox/tree/trunk)
|
||||||
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
|
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
|
||||||
- [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF)
|
- [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF)
|
||||||
- HTML, CSS, JavaScript
|
- HTML, CSS, JavaScript
|
||||||
- Docker
|
- Docker
|
||||||
- PDF.js
|
- [PDF.js](https://github.com/mozilla/pdf.js)
|
||||||
- PDF-LIB.js
|
- [PDF-LIB.js](https://github.com/Hopding/pdf-lib)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
### Locally
|
### Locally
|
||||||
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/LocalRunGuide.md
|
Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LocalRunGuide.md
|
||||||
|
|
||||||
### Docker / Podman
|
### Docker / Podman
|
||||||
https://hub.docker.com/r/frooodle/s-pdf
|
https://hub.docker.com/r/frooodle/s-pdf
|
||||||
|
|
||||||
Stirling PDF has 3 different versions, a Full version, Lite, and ultra-Lite. Depending on the types of features you use you may want a smaller image to save on space.
|
Stirling PDF has 3 different versions, a Full version, Lite, and ultra-Lite. Depending on the types of features you use you may want a smaller image to save on space.
|
||||||
To see what the different versions offer please look at our [version mapping](https://github.com/Frooodle/Stirling-PDF/blob/main/Version-groups.md)
|
To see what the different versions offer please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md)
|
||||||
For people that don't mind about space optimization just use the latest tag.
|
For people that don't mind about space optimization just use the latest tag.
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
Docker Run
|
Docker Run
|
||||||
```
|
```bash
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-v /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata \
|
-v /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata \
|
||||||
@@ -118,14 +116,14 @@ docker run -d \
|
|||||||
-e DOCKER_ENABLE_SECURITY=false \
|
-e DOCKER_ENABLE_SECURITY=false \
|
||||||
--name stirling-pdf \
|
--name stirling-pdf \
|
||||||
frooodle/s-pdf:latest
|
frooodle/s-pdf:latest
|
||||||
|
|
||||||
|
|
||||||
Can also add these for customisation but are not required
|
Can also add these for customisation but are not required
|
||||||
|
|
||||||
-v /location/of/customFiles:/customFiles \
|
-v /location/of/customFiles:/customFiles \
|
||||||
```
|
```
|
||||||
Docker Compose
|
Docker Compose
|
||||||
```
|
```yaml
|
||||||
version: '3.3'
|
version: '3.3'
|
||||||
services:
|
services:
|
||||||
stirling-pdf:
|
stirling-pdf:
|
||||||
@@ -144,17 +142,19 @@ services:
|
|||||||
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
|
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
|
||||||
|
|
||||||
## Enable OCR/Compression feature
|
## Enable OCR/Compression feature
|
||||||
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
|
Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md
|
||||||
|
|
||||||
## Want to add your own language?
|
## Supported Languages
|
||||||
Stirling PDF currently supports 21!
|
|
||||||
|
Stirling PDF currently supports 26!
|
||||||
- English (English) (en_GB)
|
- English (English) (en_GB)
|
||||||
- English (US) (en_US)
|
- English (US) (en_US)
|
||||||
- Arabic (العربية) (ar_AR)
|
- Arabic (العربية) (ar_AR)
|
||||||
- German (Deutsch) (de_DE)
|
- German (Deutsch) (de_DE)
|
||||||
- French (Français) (fr_FR)
|
- French (Français) (fr_FR)
|
||||||
- Spanish (Español) (es_ES)
|
- Spanish (Español) (es_ES)
|
||||||
- Chinese (简体中文) (zh_CN)
|
- Simplified Chinese (简体中文) (zh_CN)
|
||||||
|
- Traditional Chinese (繁體中文) (zh_TW)
|
||||||
- Catalan (Català) (ca_CA)
|
- Catalan (Català) (ca_CA)
|
||||||
- Italian (Italiano) (it_IT)
|
- Italian (Italiano) (it_IT)
|
||||||
- Swedish (Svenska) (sv_SE)
|
- Swedish (Svenska) (sv_SE)
|
||||||
@@ -170,16 +170,13 @@ Stirling PDF currently supports 21!
|
|||||||
- Turkish (Türkçe) (tr_TR)
|
- Turkish (Türkçe) (tr_TR)
|
||||||
- Indonesia (Bahasa Indonesia) (id_ID)
|
- Indonesia (Bahasa Indonesia) (id_ID)
|
||||||
- Hindi (हिंदी) (hi_IN)
|
- Hindi (हिंदी) (hi_IN)
|
||||||
|
- Hungarian (Magyar) (hu_HU)
|
||||||
|
- Bulgarian (Български) (bg_BG)
|
||||||
|
- Sebian Latin alphabet (Srpski) (sr-Latn-RS)
|
||||||
|
|
||||||
If you want to add your own language to Stirling-PDF please refer
|
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||||
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
|
|
||||||
|
|
||||||
And please create a PR to merge it back in so others can use it!
|
|
||||||
|
|
||||||
## How to View
|
|
||||||
1. Open a web browser and navigate to `http://localhost:8080/`
|
|
||||||
2. Use the application by following the instructions on the website.
|
|
||||||
|
|
||||||
|
Please see our [Contributing Guide](CONTRIBUTING.md)!
|
||||||
|
|
||||||
## Customisation
|
## Customisation
|
||||||
Stirling PDF allows easy customization of the app.
|
Stirling PDF allows easy customization of the app.
|
||||||
@@ -193,7 +190,7 @@ This file is located in the ``/configs`` directory and follows standard YAML for
|
|||||||
|
|
||||||
Environment variables are also supported and would override the settings file
|
Environment variables are also supported and would override the settings file
|
||||||
For example in the settings.yml you have
|
For example in the settings.yml you have
|
||||||
```
|
```yaml
|
||||||
system:
|
system:
|
||||||
defaultLocale: 'en-US'
|
defaultLocale: 'en-US'
|
||||||
```
|
```
|
||||||
@@ -201,7 +198,7 @@ system:
|
|||||||
To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE``
|
To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE``
|
||||||
|
|
||||||
The Current list of settings is
|
The Current list of settings is
|
||||||
```
|
```yaml
|
||||||
security:
|
security:
|
||||||
enableLogin: false # set to 'true' to enable login
|
enableLogin: false # set to 'true' to enable login
|
||||||
csrfDisabled: true
|
csrfDisabled: true
|
||||||
@@ -224,7 +221,7 @@ metrics:
|
|||||||
enabled: true # 'true' to enable Info APIs endpoints (view http://localhost:8080/swagger-ui/index.html#/API to learn more), 'false' to disable
|
enabled: true # 'true' to enable Info APIs endpoints (view http://localhost:8080/swagger-ui/index.html#/API to learn more), 'false' to disable
|
||||||
```
|
```
|
||||||
### Extra notes
|
### Extra notes
|
||||||
- Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Frooodle/Stirling-PDF/blob/main/Endpoint-groups.md)
|
- Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
|
||||||
- customStaticFilePath. Customise static files such as the app logo by placing files in the /customFiles/static/ directory. An example of customising app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF
|
- customStaticFilePath. Customise static files such as the app logo by placing files in the /customFiles/static/ directory. An example of customising app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF
|
||||||
|
|
||||||
### Environment only parameters
|
### Environment only parameters
|
||||||
@@ -234,14 +231,14 @@ metrics:
|
|||||||
|
|
||||||
## API
|
## API
|
||||||
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation
|
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation
|
||||||
[here](https://app.swaggerhub.com/apis-docs/Frooodle/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation (Or by following the API button in your settings of Stirling-PDF)
|
[here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation (Or by following the API button in your settings of Stirling-PDF)
|
||||||
|
|
||||||
|
|
||||||
## Login authentication
|
## Login authentication
|
||||||

|

|
||||||
### Prerequisites:
|
### Prerequisites:
|
||||||
- User must have the folder ./configs volumed within docker so that it is retained during updates.
|
- User must have the folder ./configs volumed within docker so that it is retained during updates.
|
||||||
- Docker uses must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables.
|
- Docker uses must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables.
|
||||||
- Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true``
|
- Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true``
|
||||||
- Now the initial user will be generated with username ``admin`` and password ``stirling``. On login you will be forced to change the password to a new one. You can also use the environment variables ``SECURITY_INITIALLOGIN_USERNAME`` and ``SECURITY_INITIALLOGIN_PASSWORD`` to set your own straight away (Recommended to remove them after user creation).
|
- Now the initial user will be generated with username ``admin`` and password ``stirling``. On login you will be forced to change the password to a new one. You can also use the environment variables ``SECURITY_INITIALLOGIN_USERNAME`` and ``SECURITY_INITIALLOGIN_PASSWORD`` to set your own straight away (Recommended to remove them after user creation).
|
||||||
|
|
||||||
@@ -262,10 +259,10 @@ For API usage you must provide a header with 'X-API-Key' and the associated API
|
|||||||
- Progress bar/Tracking
|
- Progress bar/Tracking
|
||||||
- Full custom logic pipelines to combine multiple operations together.
|
- Full custom logic pipelines to combine multiple operations together.
|
||||||
- Folder support with auto scanning to perform operations on
|
- Folder support with auto scanning to perform operations on
|
||||||
- Redact text (Via UI not just automated way)
|
- Redact text (Via UI not just automated way)
|
||||||
- Add Forms
|
- Add Forms
|
||||||
- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing
|
- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing
|
||||||
- Fill forms mannual and automatic
|
- Fill forms mannual and automatic
|
||||||
|
|
||||||
### Q2: Why is my application downloading .htm files?
|
### Q2: Why is my application downloading .htm files?
|
||||||
This is an issue caused commonly by your NGINX configuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. ``client_max_body_size SIZE;`` Where "SIZE" is 50M for example for 50MB files.
|
This is an issue caused commonly by your NGINX configuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. ``client_max_body_size SIZE;`` Where "SIZE" is 50M for example for 50MB files.
|
||||||
|
|||||||
33
build.gradle
33
build.gradle
@@ -1,21 +1,30 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'org.springframework.boot' version '3.1.2'
|
id 'org.springframework.boot' version '3.2.1'
|
||||||
id 'io.spring.dependency-management' version '1.1.3'
|
id 'io.spring.dependency-management' version '1.1.3'
|
||||||
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"
|
||||||
id 'edu.sc.seis.launch4j' version '3.0.5'
|
id 'edu.sc.seis.launch4j' version '3.0.5'
|
||||||
id 'com.diffplug.spotless' version '6.23.3'
|
id 'com.diffplug.spotless' version '6.23.3'
|
||||||
|
id 'com.github.jk1.dependency-license-report' version '2.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import com.github.jk1.license.render.*
|
||||||
|
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '0.18.1'
|
version = '0.20.1'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
licenseReport {
|
||||||
|
renderers = [new JsonReportRenderer()]
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
@@ -80,17 +89,19 @@ dependencies {
|
|||||||
//security updates
|
//security updates
|
||||||
implementation 'ch.qos.logback:logback-classic:1.4.14'
|
implementation 'ch.qos.logback:logback-classic:1.4.14'
|
||||||
implementation 'ch.qos.logback:logback-core:1.4.14'
|
implementation 'ch.qos.logback:logback-core:1.4.14'
|
||||||
implementation 'org.springframework:spring-webmvc:6.0.15'
|
implementation 'org.springframework:spring-webmvc:6.1.2'
|
||||||
|
|
||||||
implementation 'org.yaml:snakeyaml:2.1'
|
implementation 'org.yaml:snakeyaml:2.2'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.1'
|
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.1'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.1'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.1'
|
||||||
|
|
||||||
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.1'
|
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.1'
|
||||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
|
||||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.1"
|
||||||
implementation "com.h2database:h2"
|
|
||||||
|
//2.2.x requires rebuild of DB file.. need migration path
|
||||||
|
implementation "com.h2database:h2:2.1.214"
|
||||||
}
|
}
|
||||||
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.1'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.1'
|
||||||
@@ -122,15 +133,15 @@ dependencies {
|
|||||||
//general PDF
|
//general PDF
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.opencsv/opencsv
|
// https://mvnrepository.com/artifact/com.opencsv/opencsv
|
||||||
implementation ('com.opencsv:opencsv:5.7.1') {
|
implementation ('com.opencsv:opencsv:5.9') {
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation ('org.apache.pdfbox:pdfbox:2.0.29'){
|
implementation ('org.apache.pdfbox:pdfbox:3.0.1'){
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation ('org.apache.pdfbox:xmpbox:2.0.29'){
|
implementation ('org.apache.pdfbox:xmpbox:3.0.1'){
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +152,7 @@ dependencies {
|
|||||||
implementation group: 'com.google.zxing', name: 'core', version: '3.5.2'
|
implementation group: 'com.google.zxing', name: 'core', version: '3.5.2'
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||||
implementation 'org.commonmark:commonmark:0.21.0'
|
implementation 'org.commonmark:commonmark:0.21.0'
|
||||||
|
implementation 'org.commonmark:commonmark-ext-gfm-tables:0.21.0'
|
||||||
// https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core
|
// https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core
|
||||||
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0'
|
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0'
|
||||||
|
|
||||||
@@ -152,6 +164,9 @@ dependencies {
|
|||||||
tasks.withType(JavaCompile) {
|
tasks.withType(JavaCompile) {
|
||||||
dependsOn 'spotlessApply'
|
dependsOn 'spotlessApply'
|
||||||
}
|
}
|
||||||
|
compileJava {
|
||||||
|
options.compilerArgs << '-parameters'
|
||||||
|
}
|
||||||
|
|
||||||
task writeVersion {
|
task writeVersion {
|
||||||
def propsFile = file('src/main/resources/version.properties')
|
def propsFile = file('src/main/resources/version.properties')
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
appVersion: 0.14.2
|
appVersion: 0.14.2
|
||||||
description: locally hosted web application that allows you to perform various operations on PDF files
|
description: locally hosted web application that allows you to perform various operations on PDF files
|
||||||
home: https://github.com/Frooodle/Stirling-PDF
|
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||||
keywords:
|
keywords:
|
||||||
- stirling-pdf
|
- stirling-pdf
|
||||||
- helm
|
- helm
|
||||||
- charts repo
|
- charts repo
|
||||||
maintainers:
|
maintainers:
|
||||||
- name: Frooodle
|
- name: Stirling-Tools
|
||||||
url: https://github.com/Frooodle/Stirling-PDF
|
url: https://github.com/Stirling-Tools/Stirling-PDF
|
||||||
name: stirling-pdf-chart
|
name: stirling-pdf-chart
|
||||||
sources:
|
sources:
|
||||||
- https://github.com/Frooodle/Stirling-PDF
|
- https://github.com/Stirling-Tools/Stirling-PDF
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
31
exampleYmlFiles/docker-compose-latest-lite-security.yml
Normal file
31
exampleYmlFiles/docker-compose-latest-lite-security.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Lite-Security
|
||||||
|
image: frooodle/s-pdf:latest-lite
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 2G
|
||||||
|
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'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
|
||||||
|
- /stirling/latest/config:/configs:rw
|
||||||
|
- /stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF-Lite
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
restart: on-failure:5
|
||||||
30
exampleYmlFiles/docker-compose-latest-lite.yml
Normal file
30
exampleYmlFiles/docker-compose-latest-lite.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Lite
|
||||||
|
image: frooodle/s-pdf:latest-lite
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 2G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /stirling/latest/config:/configs:rw
|
||||||
|
- /stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "false"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF-Lite
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
restart: on-failure:5
|
||||||
31
exampleYmlFiles/docker-compose-latest-security.yml
Normal file
31
exampleYmlFiles/docker-compose-latest-security.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Security
|
||||||
|
image: frooodle/s-pdf:latest
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 4G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
|
||||||
|
- /stirling/latest/config:/configs:rw
|
||||||
|
- /stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
restart: on-failure:5
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Ultra-Lite-Security
|
||||||
|
image: frooodle/s-pdf:latest-ultra-lite
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 1G
|
||||||
|
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'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
|
||||||
|
- /stirling/latest/config:/configs:rw
|
||||||
|
- /stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF-Lite
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
restart: on-failure:5
|
||||||
30
exampleYmlFiles/docker-compose-latest-ultra-lite.yml
Normal file
30
exampleYmlFiles/docker-compose-latest-ultra-lite.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Ultra-Lite
|
||||||
|
image: frooodle/s-pdf:latest-ultra-lite
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 1G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /stirling/latest/config:/configs:rw
|
||||||
|
- /stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "false"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF-Ultra-lite
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Ultra-lite Latest
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF-Ultra-lite Latest
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
restart: on-failure:5
|
||||||
31
exampleYmlFiles/docker-compose-latest.yml
Normal file
31
exampleYmlFiles/docker-compose-latest.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF
|
||||||
|
image: frooodle/s-pdf:latest
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 4G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /stirling/latest/data:/usr/share/tesseract-ocr/5/tessdata:rw
|
||||||
|
- /stirling/latest/config:/configs:rw
|
||||||
|
- /stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "false"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
restart: on-failure:5
|
||||||
@@ -2,13 +2,13 @@ echo "Running Stirling PDF with DOCKER_ENABLE_SECURITY=${DOCKER_ENABLE_SECURITY}
|
|||||||
# Check for DOCKER_ENABLE_SECURITY and download the appropriate JAR if required
|
# Check for DOCKER_ENABLE_SECURITY and download the appropriate JAR if required
|
||||||
if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then
|
if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then
|
||||||
if [ ! -f app-security.jar ]; then
|
if [ ! -f app-security.jar ]; then
|
||||||
echo "Trying to download from: https://github.com/Frooodle/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar"
|
echo "Trying to download from: https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar"
|
||||||
curl -L -o app-security.jar https://github.com/Frooodle/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar
|
curl -L -o app-security.jar https://github.com/Stirling-Tools/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar
|
||||||
|
|
||||||
# If the first download attempt failed, try with the 'v' prefix
|
# If the first download attempt failed, try with the 'v' prefix
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Trying to download from: https://github.com/Frooodle/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar"
|
echo "Trying to download from: https://github.com/Stirling-Tools/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar"
|
||||||
curl -L -o app-security.jar https://github.com/Frooodle/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar
|
curl -L -o app-security.jar https://github.com/Stirling-Tools/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then # checks if curl was successful
|
if [ $? -eq 0 ]; then # checks if curl was successful
|
||||||
@@ -16,4 +16,4 @@ if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then
|
|||||||
ln -s app-security.jar app.jar
|
ln -s app-security.jar app.jar
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.pdfbox.examples.signature;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
|
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
|
||||||
|
import org.bouncycastle.cms.CMSException;
|
||||||
|
import org.bouncycastle.cms.CMSTypedData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a InputStream into a CMSProcessable object for bouncy castle. It's a memory saving
|
||||||
|
* alternative to the {@link org.bouncycastle.cms.CMSProcessableByteArray CMSProcessableByteArray}
|
||||||
|
* class.
|
||||||
|
*
|
||||||
|
* @author Thomas Chojecki
|
||||||
|
*/
|
||||||
|
class CMSProcessableInputStream implements CMSTypedData {
|
||||||
|
private final InputStream in;
|
||||||
|
private final ASN1ObjectIdentifier contentType;
|
||||||
|
|
||||||
|
CMSProcessableInputStream(InputStream is) {
|
||||||
|
this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), is);
|
||||||
|
}
|
||||||
|
|
||||||
|
CMSProcessableInputStream(ASN1ObjectIdentifier type, InputStream is) {
|
||||||
|
contentType = type;
|
||||||
|
in = is;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getContent() {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(OutputStream out) throws IOException, CMSException {
|
||||||
|
// read the content only one time
|
||||||
|
in.transferTo(out);
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ASN1ObjectIdentifier getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 The Apache Software Foundation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.pdfbox.examples.signature;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
||||||
|
import org.bouncycastle.cms.CMSException;
|
||||||
|
import org.bouncycastle.cms.CMSSignedData;
|
||||||
|
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||||
|
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
||||||
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
||||||
|
|
||||||
|
public abstract class CreateSignatureBase implements SignatureInterface {
|
||||||
|
private PrivateKey privateKey;
|
||||||
|
private Certificate[] certificateChain;
|
||||||
|
private String tsaUrl;
|
||||||
|
private boolean externalSigning;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the signature creator with a keystore (pkcs12) and pin that should be used for the
|
||||||
|
* signature.
|
||||||
|
*
|
||||||
|
* @param keystore is a pkcs12 keystore.
|
||||||
|
* @param pin is the pin for the keystore / private key
|
||||||
|
* @throws KeyStoreException if the keystore has not been initialized (loaded)
|
||||||
|
* @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
|
||||||
|
* @throws UnrecoverableKeyException if the given password is wrong
|
||||||
|
* @throws CertificateException if the certificate is not valid as signing time
|
||||||
|
* @throws IOException if no certificate could be found
|
||||||
|
*/
|
||||||
|
public CreateSignatureBase(KeyStore keystore, char[] pin)
|
||||||
|
throws KeyStoreException,
|
||||||
|
UnrecoverableKeyException,
|
||||||
|
NoSuchAlgorithmException,
|
||||||
|
IOException,
|
||||||
|
CertificateException {
|
||||||
|
// grabs the first alias from the keystore and get the private key. An
|
||||||
|
// alternative method or constructor could be used for setting a specific
|
||||||
|
// alias that should be used.
|
||||||
|
Enumeration<String> aliases = keystore.aliases();
|
||||||
|
String alias;
|
||||||
|
Certificate cert = null;
|
||||||
|
while (cert == null && aliases.hasMoreElements()) {
|
||||||
|
alias = aliases.nextElement();
|
||||||
|
setPrivateKey((PrivateKey) keystore.getKey(alias, pin));
|
||||||
|
Certificate[] certChain = keystore.getCertificateChain(alias);
|
||||||
|
if (certChain != null) {
|
||||||
|
setCertificateChain(certChain);
|
||||||
|
cert = certChain[0];
|
||||||
|
if (cert instanceof X509Certificate) {
|
||||||
|
// avoid expired certificate
|
||||||
|
((X509Certificate) cert).checkValidity();
|
||||||
|
|
||||||
|
//// SigUtils.checkCertificateUsage((X509Certificate) cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cert == null) {
|
||||||
|
throw new IOException("Could not find certificate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setPrivateKey(PrivateKey privateKey) {
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setCertificateChain(final Certificate[] certificateChain) {
|
||||||
|
this.certificateChain = certificateChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Certificate[] getCertificateChain() {
|
||||||
|
return certificateChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTsaUrl(String tsaUrl) {
|
||||||
|
this.tsaUrl = tsaUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SignatureInterface sample implementation.
|
||||||
|
*
|
||||||
|
* <p>This method will be called from inside of the pdfbox and create the PKCS #7 signature. The
|
||||||
|
* given InputStream contains the bytes that are given by the byte range.
|
||||||
|
*
|
||||||
|
* <p>This method is for internal use only.
|
||||||
|
*
|
||||||
|
* <p>Use your favorite cryptographic library to implement PKCS #7 signature creation. If you
|
||||||
|
* want to create the hash and the signature separately (e.g. to transfer only the hash to an
|
||||||
|
* external application), read <a href="https://stackoverflow.com/questions/41767351">this
|
||||||
|
* answer</a> or <a href="https://stackoverflow.com/questions/56867465">this answer</a>.
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public byte[] sign(InputStream content) throws IOException {
|
||||||
|
// cannot be done private (interface)
|
||||||
|
try {
|
||||||
|
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
||||||
|
X509Certificate cert = (X509Certificate) certificateChain[0];
|
||||||
|
ContentSigner sha1Signer =
|
||||||
|
new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey);
|
||||||
|
gen.addSignerInfoGenerator(
|
||||||
|
new JcaSignerInfoGeneratorBuilder(
|
||||||
|
new JcaDigestCalculatorProviderBuilder().build())
|
||||||
|
.build(sha1Signer, cert));
|
||||||
|
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
|
||||||
|
CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
|
||||||
|
CMSSignedData signedData = gen.generate(msg, false);
|
||||||
|
if (tsaUrl != null && !tsaUrl.isEmpty()) {
|
||||||
|
ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl);
|
||||||
|
signedData = validation.addSignedTimeStamp(signedData);
|
||||||
|
}
|
||||||
|
return signedData.getEncoded();
|
||||||
|
} catch (GeneralSecurityException
|
||||||
|
| CMSException
|
||||||
|
| OperatorCreationException
|
||||||
|
| URISyntaxException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if external signing scenario should be used. If {@code false}, SignatureInterface would
|
||||||
|
* be used for signing.
|
||||||
|
*
|
||||||
|
* <p>Default: {@code false}
|
||||||
|
*
|
||||||
|
* @param externalSigning {@code true} if external signing should be performed
|
||||||
|
*/
|
||||||
|
public void setExternalSigning(boolean externalSigning) {
|
||||||
|
this.externalSigning = externalSigning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExternalSigning() {
|
||||||
|
return externalSigning;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.pdfbox.examples.signature;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.DigestInputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
|
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
||||||
|
import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
|
||||||
|
import org.bouncycastle.tsp.TSPException;
|
||||||
|
import org.bouncycastle.tsp.TimeStampRequest;
|
||||||
|
import org.bouncycastle.tsp.TimeStampRequestGenerator;
|
||||||
|
import org.bouncycastle.tsp.TimeStampResponse;
|
||||||
|
import org.bouncycastle.tsp.TimeStampToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time Stamping Authority (TSA) Client [RFC 3161].
|
||||||
|
*
|
||||||
|
* @author Vakhtang Koroghlishvili
|
||||||
|
* @author John Hewson
|
||||||
|
*/
|
||||||
|
public class TSAClient {
|
||||||
|
private static final Logger LOG = LogManager.getLogger(TSAClient.class);
|
||||||
|
|
||||||
|
private static final DigestAlgorithmIdentifierFinder ALGORITHM_OID_FINDER =
|
||||||
|
new DefaultDigestAlgorithmIdentifierFinder();
|
||||||
|
|
||||||
|
private final URL url;
|
||||||
|
private final String username;
|
||||||
|
private final String password;
|
||||||
|
private final MessageDigest digest;
|
||||||
|
|
||||||
|
// SecureRandom.getInstanceStrong() would be better, but sometimes blocks on Linux
|
||||||
|
private static final Random RANDOM = new SecureRandom();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url the URL of the TSA service
|
||||||
|
* @param username user name of TSA
|
||||||
|
* @param password password of TSA
|
||||||
|
* @param digest the message digest to use
|
||||||
|
*/
|
||||||
|
public TSAClient(URL url, String username, String password, MessageDigest digest) {
|
||||||
|
this.url = url;
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.digest = digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param content
|
||||||
|
* @return the time stamp token
|
||||||
|
* @throws IOException if there was an error with the connection or data from the TSA server, or
|
||||||
|
* if the time stamp response could not be validated
|
||||||
|
*/
|
||||||
|
public TimeStampToken getTimeStampToken(InputStream content) throws IOException {
|
||||||
|
digest.reset();
|
||||||
|
DigestInputStream dis = new DigestInputStream(content, digest);
|
||||||
|
while (dis.read() != -1) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
byte[] hash = digest.digest();
|
||||||
|
|
||||||
|
// 32-bit cryptographic nonce
|
||||||
|
int nonce = RANDOM.nextInt();
|
||||||
|
|
||||||
|
// generate TSA request
|
||||||
|
TimeStampRequestGenerator tsaGenerator = new TimeStampRequestGenerator();
|
||||||
|
tsaGenerator.setCertReq(true);
|
||||||
|
ASN1ObjectIdentifier oid = ALGORITHM_OID_FINDER.find(digest.getAlgorithm()).getAlgorithm();
|
||||||
|
TimeStampRequest request = tsaGenerator.generate(oid, hash, BigInteger.valueOf(nonce));
|
||||||
|
|
||||||
|
// get TSA response
|
||||||
|
byte[] tsaResponse = getTSAResponse(request.getEncoded());
|
||||||
|
|
||||||
|
TimeStampResponse response;
|
||||||
|
try {
|
||||||
|
response = new TimeStampResponse(tsaResponse);
|
||||||
|
response.validate(request);
|
||||||
|
} catch (TSPException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeStampToken timeStampToken = response.getTimeStampToken();
|
||||||
|
if (timeStampToken == null) {
|
||||||
|
// https://www.ietf.org/rfc/rfc3161.html#section-2.4.2
|
||||||
|
throw new IOException(
|
||||||
|
"Response from "
|
||||||
|
+ url
|
||||||
|
+ " does not have a time stamp token, status: "
|
||||||
|
+ response.getStatus()
|
||||||
|
+ " ("
|
||||||
|
+ response.getStatusString()
|
||||||
|
+ ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeStampToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets response data for the given encoded TimeStampRequest data
|
||||||
|
// throws IOException if a connection to the TSA cannot be established
|
||||||
|
private byte[] getTSAResponse(byte[] request) throws IOException {
|
||||||
|
LOG.debug("Opening connection to TSA server");
|
||||||
|
|
||||||
|
// todo: support proxy servers
|
||||||
|
URLConnection connection = url.openConnection();
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setDoInput(true);
|
||||||
|
connection.setRequestProperty("Content-Type", "application/timestamp-query");
|
||||||
|
|
||||||
|
LOG.debug("Established connection to TSA server");
|
||||||
|
|
||||||
|
if (username != null && password != null && !username.isEmpty() && !password.isEmpty()) {
|
||||||
|
String contentEncoding = connection.getContentEncoding();
|
||||||
|
if (contentEncoding == null) {
|
||||||
|
contentEncoding = StandardCharsets.UTF_8.name();
|
||||||
|
}
|
||||||
|
connection.setRequestProperty(
|
||||||
|
"Authorization",
|
||||||
|
"Basic "
|
||||||
|
+ new String(
|
||||||
|
Base64.getEncoder()
|
||||||
|
.encode(
|
||||||
|
(username + ":" + password)
|
||||||
|
.getBytes(contentEncoding))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// read response
|
||||||
|
try (OutputStream output = connection.getOutputStream()) {
|
||||||
|
output.write(request);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOG.error("Exception when writing to {}", this.url, ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Waiting for response from TSA server");
|
||||||
|
|
||||||
|
byte[] response;
|
||||||
|
try (InputStream input = connection.getInputStream()) {
|
||||||
|
response = input.readAllBytes();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOG.error("Exception when reading from {}", this.url, ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Received response from TSA server");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.pdfbox.examples.signature;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.ASN1Encodable;
|
||||||
|
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||||
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
|
import org.bouncycastle.asn1.ASN1Primitive;
|
||||||
|
import org.bouncycastle.asn1.DERSet;
|
||||||
|
import org.bouncycastle.asn1.cms.Attribute;
|
||||||
|
import org.bouncycastle.asn1.cms.AttributeTable;
|
||||||
|
import org.bouncycastle.asn1.cms.Attributes;
|
||||||
|
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||||
|
import org.bouncycastle.cms.CMSSignedData;
|
||||||
|
import org.bouncycastle.cms.SignerInformation;
|
||||||
|
import org.bouncycastle.cms.SignerInformationStore;
|
||||||
|
import org.bouncycastle.tsp.TimeStampToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class wraps the TSAClient and the work that has to be done with it. Like Adding Signed
|
||||||
|
* TimeStamps to a signature, or creating a CMS timestamp attribute (with a signed timestamp)
|
||||||
|
*
|
||||||
|
* @author Others
|
||||||
|
* @author Alexis Suter
|
||||||
|
*/
|
||||||
|
public class ValidationTimeStamp {
|
||||||
|
private TSAClient tsaClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param tsaUrl The url where TS-Request will be done.
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* @throws MalformedURLException
|
||||||
|
* @throws java.net.URISyntaxException
|
||||||
|
*/
|
||||||
|
public ValidationTimeStamp(String tsaUrl)
|
||||||
|
throws NoSuchAlgorithmException, MalformedURLException, URISyntaxException {
|
||||||
|
if (tsaUrl != null) {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
this.tsaClient = new TSAClient(new URI(tsaUrl).toURL(), null, null, digest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a signed timestamp token by the given input stream.
|
||||||
|
*
|
||||||
|
* @param content InputStream of the content to sign
|
||||||
|
* @return the byte[] of the timestamp token
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public byte[] getTimeStampToken(InputStream content) throws IOException {
|
||||||
|
TimeStampToken timeStampToken = tsaClient.getTimeStampToken(content);
|
||||||
|
return timeStampToken.getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend cms signed data with TimeStamp first or to all signers
|
||||||
|
*
|
||||||
|
* @param signedData Generated CMS signed data
|
||||||
|
* @return CMSSignedData Extended CMS signed data
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public CMSSignedData addSignedTimeStamp(CMSSignedData signedData) throws IOException {
|
||||||
|
SignerInformationStore signerStore = signedData.getSignerInfos();
|
||||||
|
List<SignerInformation> newSigners = new ArrayList<>();
|
||||||
|
|
||||||
|
for (SignerInformation signer : signerStore.getSigners()) {
|
||||||
|
// This adds a timestamp to every signer (into his unsigned attributes) in the
|
||||||
|
// signature.
|
||||||
|
newSigners.add(signTimeStamp(signer));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because new SignerInformation is created, new SignerInfoStore has to be created
|
||||||
|
// and also be replaced in signedData. Which creates a new signedData object.
|
||||||
|
return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(newSigners));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend CMS Signer Information with the TimeStampToken into the unsigned Attributes.
|
||||||
|
*
|
||||||
|
* @param signer information about signer
|
||||||
|
* @return information about SignerInformation
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private SignerInformation signTimeStamp(SignerInformation signer) throws IOException {
|
||||||
|
AttributeTable unsignedAttributes = signer.getUnsignedAttributes();
|
||||||
|
|
||||||
|
ASN1EncodableVector vector = new ASN1EncodableVector();
|
||||||
|
if (unsignedAttributes != null) {
|
||||||
|
vector = unsignedAttributes.toASN1EncodableVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeStampToken timeStampToken =
|
||||||
|
tsaClient.getTimeStampToken(new ByteArrayInputStream(signer.getSignature()));
|
||||||
|
byte[] token = timeStampToken.getEncoded();
|
||||||
|
ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;
|
||||||
|
ASN1Encodable signatureTimeStamp =
|
||||||
|
new Attribute(oid, new DERSet(ASN1Primitive.fromByteArray(token)));
|
||||||
|
|
||||||
|
vector.add(signatureTimeStamp);
|
||||||
|
Attributes signedAttributes = new Attributes(vector);
|
||||||
|
|
||||||
|
// There is no other way changing the unsigned attributes of the signer information.
|
||||||
|
// result is never null, new SignerInformation always returned,
|
||||||
|
// see source code of replaceUnsignedAttributes
|
||||||
|
return SignerInformation.replaceUnsignedAttributes(
|
||||||
|
signer, new AttributeTable(signedAttributes));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.pdfbox.examples.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate class to close the connection when the class gets closed.
|
||||||
|
*
|
||||||
|
* @author Tilman Hausherr
|
||||||
|
*/
|
||||||
|
public class ConnectedInputStream extends InputStream {
|
||||||
|
HttpURLConnection con;
|
||||||
|
InputStream is;
|
||||||
|
|
||||||
|
public ConnectedInputStream(HttpURLConnection con, InputStream is) {
|
||||||
|
this.con = con;
|
||||||
|
this.is = is;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
return is.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b) throws IOException {
|
||||||
|
return is.read(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
return is.read(b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
return is.skip(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
return is.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void mark(int readlimit) {
|
||||||
|
is.mark(readlimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
is.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean markSupported() {
|
||||||
|
return is.markSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
is.close();
|
||||||
|
con.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,81 +1,81 @@
|
|||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import stirling.software.SPDF.config.ConfigInitializer;
|
import stirling.software.SPDF.config.ConfigInitializer;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
public class SPdfApplication {
|
public class SPdfApplication {
|
||||||
|
|
||||||
@Autowired private Environment env;
|
@Autowired private Environment env;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
// Check if the BROWSER_OPEN environment variable is set to true
|
// Check if the BROWSER_OPEN environment variable is set to true
|
||||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||||
boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true");
|
boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true");
|
||||||
|
|
||||||
if (browserOpen) {
|
if (browserOpen) {
|
||||||
try {
|
try {
|
||||||
String url = "http://localhost:" + getPort();
|
String url = "http://localhost:" + getPort();
|
||||||
|
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
Runtime rt = Runtime.getRuntime();
|
Runtime rt = Runtime.getRuntime();
|
||||||
if (os.contains("win")) {
|
if (os.contains("win")) {
|
||||||
// For Windows
|
// For Windows
|
||||||
rt.exec("rundll32 url.dll,FileProtocolHandler " + url);
|
rt.exec("rundll32 url.dll,FileProtocolHandler " + url);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
||||||
app.addInitializers(new ConfigInitializer());
|
app.addInitializers(new ConfigInitializer());
|
||||||
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
||||||
app.setDefaultProperties(
|
app.setDefaultProperties(
|
||||||
Collections.singletonMap(
|
Collections.singletonMap(
|
||||||
"spring.config.additional-location", "file:configs/settings.yml"));
|
"spring.config.additional-location", "file:configs/settings.yml"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(
|
System.out.println(
|
||||||
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
|
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
|
||||||
}
|
}
|
||||||
app.run(args);
|
app.run(args);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// TODO Auto-generated catch block
|
// TODO Auto-generated catch block
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
GeneralUtils.createDir("customFiles/static/");
|
GeneralUtils.createDir("customFiles/static/");
|
||||||
GeneralUtils.createDir("customFiles/templates/");
|
GeneralUtils.createDir("customFiles/templates/");
|
||||||
|
|
||||||
System.out.println("Stirling-PDF Started.");
|
System.out.println("Stirling-PDF Started.");
|
||||||
|
|
||||||
String url = "http://localhost:" + getPort();
|
String url = "http://localhost:" + getPort();
|
||||||
System.out.println("Navigate to " + url);
|
System.out.println("Navigate to " + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getPort() {
|
public static String getPort() {
|
||||||
String port = System.getProperty("local.server.port");
|
String port = System.getProperty("local.server.port");
|
||||||
if (port == null || port.isEmpty()) {
|
if (port == null || port.isEmpty()) {
|
||||||
port = "8080";
|
port = "8080";
|
||||||
}
|
}
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,92 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import java.io.IOException;
|
||||||
import org.springframework.context.annotation.Bean;
|
import java.nio.file.Files;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Properties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@Configuration
|
import org.springframework.context.annotation.Bean;
|
||||||
public class AppConfig {
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
@Bean(name = "loginEnabled")
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
public boolean loginEnabled() {
|
|
||||||
return applicationProperties.getSecurity().getEnableLogin();
|
@Configuration
|
||||||
}
|
public class AppConfig {
|
||||||
|
|
||||||
@Bean(name = "appName")
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
public String appName() {
|
|
||||||
String homeTitle = applicationProperties.getUi().getAppName();
|
@Bean(name = "loginEnabled")
|
||||||
return (homeTitle != null) ? homeTitle : "Stirling PDF";
|
public boolean loginEnabled() {
|
||||||
}
|
return applicationProperties.getSecurity().getEnableLogin();
|
||||||
|
}
|
||||||
@Bean(name = "appVersion")
|
|
||||||
public String appVersion() {
|
@Bean(name = "appName")
|
||||||
String version = getClass().getPackage().getImplementationVersion();
|
public String appName() {
|
||||||
return (version != null) ? version : "0.0.0";
|
String homeTitle = applicationProperties.getUi().getAppName();
|
||||||
}
|
return (homeTitle != null) ? homeTitle : "Stirling PDF";
|
||||||
|
}
|
||||||
@Bean(name = "homeText")
|
|
||||||
public String homeText() {
|
@Bean(name = "appVersion")
|
||||||
return (applicationProperties.getUi().getHomeDescription() != null)
|
public String appVersion() {
|
||||||
? applicationProperties.getUi().getHomeDescription()
|
Resource resource = new ClassPathResource("version.properties");
|
||||||
: "null";
|
Properties props = new Properties();
|
||||||
}
|
try {
|
||||||
|
props.load(resource.getInputStream());
|
||||||
@Bean(name = "navBarText")
|
return props.getProperty("version");
|
||||||
public String navBarText() {
|
} catch (IOException e) {
|
||||||
String defaultNavBar =
|
e.printStackTrace();
|
||||||
applicationProperties.getUi().getAppNameNavbar() != null
|
}
|
||||||
? applicationProperties.getUi().getAppNameNavbar()
|
return "0.0.0";
|
||||||
: applicationProperties.getUi().getAppName();
|
}
|
||||||
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
|
|
||||||
}
|
@Bean(name = "homeText")
|
||||||
|
public String homeText() {
|
||||||
@Bean(name = "enableAlphaFunctionality")
|
return (applicationProperties.getUi().getHomeDescription() != null)
|
||||||
public boolean enableAlphaFunctionality() {
|
? applicationProperties.getUi().getHomeDescription()
|
||||||
return applicationProperties.getSystem().getEnableAlphaFunctionality() != null
|
: "null";
|
||||||
? applicationProperties.getSystem().getEnableAlphaFunctionality()
|
}
|
||||||
: false;
|
|
||||||
}
|
@Bean(name = "navBarText")
|
||||||
|
public String navBarText() {
|
||||||
@Bean(name = "rateLimit")
|
String defaultNavBar =
|
||||||
public boolean rateLimit() {
|
applicationProperties.getUi().getAppNameNavbar() != null
|
||||||
String appName = System.getProperty("rateLimit");
|
? applicationProperties.getUi().getAppNameNavbar()
|
||||||
if (appName == null) appName = System.getenv("rateLimit");
|
: applicationProperties.getUi().getAppName();
|
||||||
return (appName != null) ? Boolean.valueOf(appName) : false;
|
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Bean(name = "enableAlphaFunctionality")
|
||||||
|
public boolean enableAlphaFunctionality() {
|
||||||
|
return applicationProperties.getSystem().getEnableAlphaFunctionality() != null
|
||||||
|
? applicationProperties.getSystem().getEnableAlphaFunctionality()
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "rateLimit")
|
||||||
|
public boolean rateLimit() {
|
||||||
|
String appName = System.getProperty("rateLimit");
|
||||||
|
if (appName == null) appName = System.getenv("rateLimit");
|
||||||
|
return (appName != null) ? Boolean.valueOf(appName) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "RunningInDocker")
|
||||||
|
public boolean runningInDocker() {
|
||||||
|
return Files.exists(Paths.get("/.dockerenv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "bookFormatsInstalled")
|
||||||
|
public boolean bookFormatsInstalled() {
|
||||||
|
return applicationProperties.getSystem().getCustomApplications().isInstallBookFormats();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "htmlFormatsInstalled")
|
||||||
|
public boolean htmlFormatsInstalled() {
|
||||||
|
return applicationProperties
|
||||||
|
.getSystem()
|
||||||
|
.getCustomApplications()
|
||||||
|
.isInstallAdvancedHtmlToPDF();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,64 +1,64 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.LocaleResolver;
|
import org.springframework.web.servlet.LocaleResolver;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class Beans implements WebMvcConfigurer {
|
public class Beans implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(localeChangeInterceptor());
|
registry.addInterceptor(localeChangeInterceptor());
|
||||||
registry.addInterceptor(new CleanUrlInterceptor());
|
registry.addInterceptor(new CleanUrlInterceptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public LocaleChangeInterceptor localeChangeInterceptor() {
|
public LocaleChangeInterceptor localeChangeInterceptor() {
|
||||||
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
|
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
|
||||||
lci.setParamName("lang");
|
lci.setParamName("lang");
|
||||||
return lci;
|
return lci;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public LocaleResolver localeResolver() {
|
public LocaleResolver localeResolver() {
|
||||||
SessionLocaleResolver slr = new SessionLocaleResolver();
|
SessionLocaleResolver slr = new SessionLocaleResolver();
|
||||||
|
|
||||||
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();
|
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();
|
||||||
Locale defaultLocale =
|
Locale defaultLocale =
|
||||||
Locale.UK; // Fallback to UK locale if environment variable is not set
|
Locale.UK; // Fallback to UK locale if environment variable is not set
|
||||||
|
|
||||||
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
||||||
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
|
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
|
||||||
String tempLanguageTag = tempLocale.toLanguageTag();
|
String tempLanguageTag = tempLocale.toLanguageTag();
|
||||||
|
|
||||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
||||||
defaultLocale = tempLocale;
|
defaultLocale = tempLocale;
|
||||||
} else {
|
} else {
|
||||||
tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-"));
|
tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-"));
|
||||||
tempLanguageTag = tempLocale.toLanguageTag();
|
tempLanguageTag = tempLocale.toLanguageTag();
|
||||||
|
|
||||||
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
|
||||||
defaultLocale = tempLocale;
|
defaultLocale = tempLocale;
|
||||||
} else {
|
} else {
|
||||||
System.err.println(
|
System.err.println(
|
||||||
"Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK.");
|
"Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slr.setDefaultLocale(defaultLocale);
|
slr.setDefaultLocale(defaultLocale);
|
||||||
return slr;
|
return slr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +1,74 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
public class CleanUrlInterceptor implements HandlerInterceptor {
|
public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
private static final List<String> ALLOWED_PARAMS =
|
private static final List<String> ALLOWED_PARAMS =
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
"lang", "endpoint", "endpoints", "logout", "error", "file", "messageType");
|
"lang", "endpoint", "endpoints", "logout", "error", "file", "messageType");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(
|
public boolean preHandle(
|
||||||
HttpServletRequest request, HttpServletResponse response, Object handler)
|
HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
String queryString = request.getQueryString();
|
String queryString = request.getQueryString();
|
||||||
if (queryString != null && !queryString.isEmpty()) {
|
if (queryString != null && !queryString.isEmpty()) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
Map<String, String> parameters = new HashMap<>();
|
Map<String, String> parameters = new HashMap<>();
|
||||||
|
|
||||||
// Keep only the allowed parameters
|
// Keep only the allowed parameters
|
||||||
String[] queryParameters = queryString.split("&");
|
String[] queryParameters = queryString.split("&");
|
||||||
for (String param : queryParameters) {
|
for (String param : queryParameters) {
|
||||||
String[] keyValue = param.split("=");
|
String[] keyValue = param.split("=");
|
||||||
if (keyValue.length != 2) {
|
if (keyValue.length != 2) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ALLOWED_PARAMS.contains(keyValue[0])) {
|
if (ALLOWED_PARAMS.contains(keyValue[0])) {
|
||||||
parameters.put(keyValue[0], keyValue[1]);
|
parameters.put(keyValue[0], keyValue[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are any parameters that are not allowed
|
// If there are any parameters that are not allowed
|
||||||
if (parameters.size() != queryParameters.length) {
|
if (parameters.size() != queryParameters.length) {
|
||||||
// Construct new query string
|
// Construct new query string
|
||||||
StringBuilder newQueryString = new StringBuilder();
|
StringBuilder newQueryString = new StringBuilder();
|
||||||
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
||||||
if (newQueryString.length() > 0) {
|
if (newQueryString.length() > 0) {
|
||||||
newQueryString.append("&");
|
newQueryString.append("&");
|
||||||
}
|
}
|
||||||
newQueryString.append(entry.getKey()).append("=").append(entry.getValue());
|
newQueryString.append(entry.getKey()).append("=").append(entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to the URL with only allowed query parameters
|
// Redirect to the URL with only allowed query parameters
|
||||||
String redirectUrl = requestURI + "?" + newQueryString;
|
String redirectUrl = requestURI + "?" + newQueryString;
|
||||||
response.sendRedirect(redirectUrl);
|
response.sendRedirect(redirectUrl);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postHandle(
|
public void postHandle(
|
||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
Object handler,
|
Object handler,
|
||||||
ModelAndView modelAndView) {}
|
ModelAndView modelAndView) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterCompletion(
|
public void afterCompletion(
|
||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
Object handler,
|
Object handler,
|
||||||
Exception ex) {}
|
Exception ex) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,231 +1,247 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
public class EndpointConfiguration {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
|
@Service
|
||||||
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
@DependsOn({"bookFormatsInstalled"})
|
||||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
public class EndpointConfiguration {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
|
||||||
private final ApplicationProperties applicationProperties;
|
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
||||||
|
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
||||||
@Autowired
|
|
||||||
public EndpointConfiguration(ApplicationProperties applicationProperties) {
|
private final ApplicationProperties applicationProperties;
|
||||||
this.applicationProperties = applicationProperties;
|
|
||||||
init();
|
private boolean bookFormatsInstalled;
|
||||||
processEnvironmentConfigs();
|
|
||||||
}
|
@Autowired
|
||||||
|
public EndpointConfiguration(
|
||||||
public void enableEndpoint(String endpoint) {
|
ApplicationProperties applicationProperties,
|
||||||
endpointStatuses.put(endpoint, true);
|
@Qualifier("bookFormatsInstalled") boolean bookFormatsInstalled) {
|
||||||
}
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.bookFormatsInstalled = bookFormatsInstalled;
|
||||||
public void disableEndpoint(String endpoint) {
|
init();
|
||||||
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
|
processEnvironmentConfigs();
|
||||||
logger.info("Disabling {}", endpoint);
|
}
|
||||||
endpointStatuses.put(endpoint, false);
|
|
||||||
}
|
public void enableEndpoint(String endpoint) {
|
||||||
}
|
endpointStatuses.put(endpoint, true);
|
||||||
|
}
|
||||||
public boolean isEndpointEnabled(String endpoint) {
|
|
||||||
if (endpoint.startsWith("/")) {
|
public void disableEndpoint(String endpoint) {
|
||||||
endpoint = endpoint.substring(1);
|
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
|
||||||
}
|
logger.info("Disabling {}", endpoint);
|
||||||
return endpointStatuses.getOrDefault(endpoint, true);
|
endpointStatuses.put(endpoint, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public void addEndpointToGroup(String group, String endpoint) {
|
|
||||||
endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint);
|
public boolean isEndpointEnabled(String endpoint) {
|
||||||
}
|
if (endpoint.startsWith("/")) {
|
||||||
|
endpoint = endpoint.substring(1);
|
||||||
public void enableGroup(String group) {
|
}
|
||||||
Set<String> endpoints = endpointGroups.get(group);
|
return endpointStatuses.getOrDefault(endpoint, true);
|
||||||
if (endpoints != null) {
|
}
|
||||||
for (String endpoint : endpoints) {
|
|
||||||
enableEndpoint(endpoint);
|
public void addEndpointToGroup(String group, String endpoint) {
|
||||||
}
|
endpointGroups.computeIfAbsent(group, k -> new HashSet<>()).add(endpoint);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public void enableGroup(String group) {
|
||||||
public void disableGroup(String group) {
|
Set<String> endpoints = endpointGroups.get(group);
|
||||||
Set<String> endpoints = endpointGroups.get(group);
|
if (endpoints != null) {
|
||||||
if (endpoints != null) {
|
for (String endpoint : endpoints) {
|
||||||
for (String endpoint : endpoints) {
|
enableEndpoint(endpoint);
|
||||||
disableEndpoint(endpoint);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public void disableGroup(String group) {
|
||||||
public void init() {
|
Set<String> endpoints = endpointGroups.get(group);
|
||||||
// Adding endpoints to "PageOps" group
|
if (endpoints != null) {
|
||||||
addEndpointToGroup("PageOps", "remove-pages");
|
for (String endpoint : endpoints) {
|
||||||
addEndpointToGroup("PageOps", "merge-pdfs");
|
disableEndpoint(endpoint);
|
||||||
addEndpointToGroup("PageOps", "split-pdfs");
|
}
|
||||||
addEndpointToGroup("PageOps", "pdf-organizer");
|
}
|
||||||
addEndpointToGroup("PageOps", "rotate-pdf");
|
}
|
||||||
addEndpointToGroup("PageOps", "multi-page-layout");
|
|
||||||
addEndpointToGroup("PageOps", "scale-pages");
|
public void init() {
|
||||||
addEndpointToGroup("PageOps", "adjust-contrast");
|
// Adding endpoints to "PageOps" group
|
||||||
addEndpointToGroup("PageOps", "crop");
|
addEndpointToGroup("PageOps", "remove-pages");
|
||||||
addEndpointToGroup("PageOps", "auto-split-pdf");
|
addEndpointToGroup("PageOps", "merge-pdfs");
|
||||||
addEndpointToGroup("PageOps", "extract-page");
|
addEndpointToGroup("PageOps", "split-pdfs");
|
||||||
addEndpointToGroup("PageOps", "pdf-to-single-page");
|
addEndpointToGroup("PageOps", "pdf-organizer");
|
||||||
addEndpointToGroup("PageOps", "split-by-size-or-count");
|
addEndpointToGroup("PageOps", "rotate-pdf");
|
||||||
addEndpointToGroup("PageOps", "overlay-pdf");
|
addEndpointToGroup("PageOps", "multi-page-layout");
|
||||||
addEndpointToGroup("PageOps", "split-pdf-by-sections");
|
addEndpointToGroup("PageOps", "scale-pages");
|
||||||
|
addEndpointToGroup("PageOps", "adjust-contrast");
|
||||||
// Adding endpoints to "Convert" group
|
addEndpointToGroup("PageOps", "crop");
|
||||||
addEndpointToGroup("Convert", "pdf-to-img");
|
addEndpointToGroup("PageOps", "auto-split-pdf");
|
||||||
addEndpointToGroup("Convert", "img-to-pdf");
|
addEndpointToGroup("PageOps", "extract-page");
|
||||||
addEndpointToGroup("Convert", "pdf-to-pdfa");
|
addEndpointToGroup("PageOps", "pdf-to-single-page");
|
||||||
addEndpointToGroup("Convert", "file-to-pdf");
|
addEndpointToGroup("PageOps", "split-by-size-or-count");
|
||||||
addEndpointToGroup("Convert", "xlsx-to-pdf");
|
addEndpointToGroup("PageOps", "overlay-pdf");
|
||||||
addEndpointToGroup("Convert", "pdf-to-word");
|
addEndpointToGroup("PageOps", "split-pdf-by-sections");
|
||||||
addEndpointToGroup("Convert", "pdf-to-presentation");
|
|
||||||
addEndpointToGroup("Convert", "pdf-to-text");
|
// Adding endpoints to "Convert" group
|
||||||
addEndpointToGroup("Convert", "pdf-to-html");
|
addEndpointToGroup("Convert", "pdf-to-img");
|
||||||
addEndpointToGroup("Convert", "pdf-to-xml");
|
addEndpointToGroup("Convert", "img-to-pdf");
|
||||||
addEndpointToGroup("Convert", "html-to-pdf");
|
addEndpointToGroup("Convert", "pdf-to-pdfa");
|
||||||
addEndpointToGroup("Convert", "url-to-pdf");
|
addEndpointToGroup("Convert", "file-to-pdf");
|
||||||
addEndpointToGroup("Convert", "markdown-to-pdf");
|
addEndpointToGroup("Convert", "xlsx-to-pdf");
|
||||||
addEndpointToGroup("Convert", "pdf-to-csv");
|
addEndpointToGroup("Convert", "pdf-to-word");
|
||||||
|
addEndpointToGroup("Convert", "pdf-to-presentation");
|
||||||
// Adding endpoints to "Security" group
|
addEndpointToGroup("Convert", "pdf-to-text");
|
||||||
addEndpointToGroup("Security", "add-password");
|
addEndpointToGroup("Convert", "pdf-to-html");
|
||||||
addEndpointToGroup("Security", "remove-password");
|
addEndpointToGroup("Convert", "pdf-to-xml");
|
||||||
addEndpointToGroup("Security", "change-permissions");
|
addEndpointToGroup("Convert", "html-to-pdf");
|
||||||
addEndpointToGroup("Security", "add-watermark");
|
addEndpointToGroup("Convert", "url-to-pdf");
|
||||||
addEndpointToGroup("Security", "cert-sign");
|
addEndpointToGroup("Convert", "markdown-to-pdf");
|
||||||
addEndpointToGroup("Security", "sanitize-pdf");
|
addEndpointToGroup("Convert", "pdf-to-csv");
|
||||||
addEndpointToGroup("Security", "auto-redact");
|
|
||||||
|
// Adding endpoints to "Security" group
|
||||||
// Adding endpoints to "Other" group
|
addEndpointToGroup("Security", "add-password");
|
||||||
addEndpointToGroup("Other", "ocr-pdf");
|
addEndpointToGroup("Security", "remove-password");
|
||||||
addEndpointToGroup("Other", "add-image");
|
addEndpointToGroup("Security", "change-permissions");
|
||||||
addEndpointToGroup("Other", "compress-pdf");
|
addEndpointToGroup("Security", "add-watermark");
|
||||||
addEndpointToGroup("Other", "extract-images");
|
addEndpointToGroup("Security", "cert-sign");
|
||||||
addEndpointToGroup("Other", "change-metadata");
|
addEndpointToGroup("Security", "sanitize-pdf");
|
||||||
addEndpointToGroup("Other", "extract-image-scans");
|
addEndpointToGroup("Security", "auto-redact");
|
||||||
addEndpointToGroup("Other", "sign");
|
|
||||||
addEndpointToGroup("Other", "flatten");
|
// Adding endpoints to "Other" group
|
||||||
addEndpointToGroup("Other", "repair");
|
addEndpointToGroup("Other", "ocr-pdf");
|
||||||
addEndpointToGroup("Other", "remove-blanks");
|
addEndpointToGroup("Other", "add-image");
|
||||||
addEndpointToGroup("Other", "remove-annotations");
|
addEndpointToGroup("Other", "compress-pdf");
|
||||||
addEndpointToGroup("Other", "compare");
|
addEndpointToGroup("Other", "extract-images");
|
||||||
addEndpointToGroup("Other", "add-page-numbers");
|
addEndpointToGroup("Other", "change-metadata");
|
||||||
addEndpointToGroup("Other", "auto-rename");
|
addEndpointToGroup("Other", "extract-image-scans");
|
||||||
addEndpointToGroup("Other", "get-info-on-pdf");
|
addEndpointToGroup("Other", "sign");
|
||||||
addEndpointToGroup("Other", "show-javascript");
|
addEndpointToGroup("Other", "flatten");
|
||||||
|
addEndpointToGroup("Other", "repair");
|
||||||
// CLI
|
addEndpointToGroup("Other", "remove-blanks");
|
||||||
addEndpointToGroup("CLI", "compress-pdf");
|
addEndpointToGroup("Other", "remove-annotations");
|
||||||
addEndpointToGroup("CLI", "extract-image-scans");
|
addEndpointToGroup("Other", "compare");
|
||||||
addEndpointToGroup("CLI", "remove-blanks");
|
addEndpointToGroup("Other", "add-page-numbers");
|
||||||
addEndpointToGroup("CLI", "repair");
|
addEndpointToGroup("Other", "auto-rename");
|
||||||
addEndpointToGroup("CLI", "pdf-to-pdfa");
|
addEndpointToGroup("Other", "get-info-on-pdf");
|
||||||
addEndpointToGroup("CLI", "file-to-pdf");
|
addEndpointToGroup("Other", "show-javascript");
|
||||||
addEndpointToGroup("CLI", "xlsx-to-pdf");
|
|
||||||
addEndpointToGroup("CLI", "pdf-to-word");
|
// CLI
|
||||||
addEndpointToGroup("CLI", "pdf-to-presentation");
|
addEndpointToGroup("CLI", "compress-pdf");
|
||||||
addEndpointToGroup("CLI", "pdf-to-text");
|
addEndpointToGroup("CLI", "extract-image-scans");
|
||||||
addEndpointToGroup("CLI", "pdf-to-html");
|
addEndpointToGroup("CLI", "remove-blanks");
|
||||||
addEndpointToGroup("CLI", "pdf-to-xml");
|
addEndpointToGroup("CLI", "repair");
|
||||||
addEndpointToGroup("CLI", "ocr-pdf");
|
addEndpointToGroup("CLI", "pdf-to-pdfa");
|
||||||
addEndpointToGroup("CLI", "html-to-pdf");
|
addEndpointToGroup("CLI", "file-to-pdf");
|
||||||
addEndpointToGroup("CLI", "url-to-pdf");
|
addEndpointToGroup("CLI", "xlsx-to-pdf");
|
||||||
|
addEndpointToGroup("CLI", "pdf-to-word");
|
||||||
// python
|
addEndpointToGroup("CLI", "pdf-to-presentation");
|
||||||
addEndpointToGroup("Python", "extract-image-scans");
|
addEndpointToGroup("CLI", "pdf-to-text");
|
||||||
addEndpointToGroup("Python", "remove-blanks");
|
addEndpointToGroup("CLI", "pdf-to-html");
|
||||||
addEndpointToGroup("Python", "html-to-pdf");
|
addEndpointToGroup("CLI", "pdf-to-xml");
|
||||||
addEndpointToGroup("Python", "url-to-pdf");
|
addEndpointToGroup("CLI", "ocr-pdf");
|
||||||
|
addEndpointToGroup("CLI", "html-to-pdf");
|
||||||
// openCV
|
addEndpointToGroup("CLI", "url-to-pdf");
|
||||||
addEndpointToGroup("OpenCV", "extract-image-scans");
|
addEndpointToGroup("CLI", "book-to-pdf");
|
||||||
addEndpointToGroup("OpenCV", "remove-blanks");
|
addEndpointToGroup("CLI", "pdf-to-book");
|
||||||
|
|
||||||
// LibreOffice
|
// Calibre
|
||||||
addEndpointToGroup("LibreOffice", "repair");
|
addEndpointToGroup("Calibre", "book-to-pdf");
|
||||||
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
addEndpointToGroup("Calibre", "pdf-to-book");
|
||||||
addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
|
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
// python
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
addEndpointToGroup("Python", "extract-image-scans");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-text");
|
addEndpointToGroup("Python", "remove-blanks");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
addEndpointToGroup("Python", "html-to-pdf");
|
||||||
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
addEndpointToGroup("Python", "url-to-pdf");
|
||||||
|
|
||||||
// OCRmyPDF
|
// openCV
|
||||||
addEndpointToGroup("OCRmyPDF", "compress-pdf");
|
addEndpointToGroup("OpenCV", "extract-image-scans");
|
||||||
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
|
addEndpointToGroup("OpenCV", "remove-blanks");
|
||||||
addEndpointToGroup("OCRmyPDF", "ocr-pdf");
|
|
||||||
|
// LibreOffice
|
||||||
// Java
|
addEndpointToGroup("LibreOffice", "repair");
|
||||||
addEndpointToGroup("Java", "merge-pdfs");
|
addEndpointToGroup("LibreOffice", "file-to-pdf");
|
||||||
addEndpointToGroup("Java", "remove-pages");
|
addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
|
||||||
addEndpointToGroup("Java", "split-pdfs");
|
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
||||||
addEndpointToGroup("Java", "pdf-organizer");
|
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
||||||
addEndpointToGroup("Java", "rotate-pdf");
|
addEndpointToGroup("LibreOffice", "pdf-to-text");
|
||||||
addEndpointToGroup("Java", "pdf-to-img");
|
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
||||||
addEndpointToGroup("Java", "img-to-pdf");
|
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
||||||
addEndpointToGroup("Java", "add-password");
|
|
||||||
addEndpointToGroup("Java", "remove-password");
|
// OCRmyPDF
|
||||||
addEndpointToGroup("Java", "change-permissions");
|
addEndpointToGroup("OCRmyPDF", "compress-pdf");
|
||||||
addEndpointToGroup("Java", "add-watermark");
|
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
|
||||||
addEndpointToGroup("Java", "add-image");
|
addEndpointToGroup("OCRmyPDF", "ocr-pdf");
|
||||||
addEndpointToGroup("Java", "extract-images");
|
|
||||||
addEndpointToGroup("Java", "change-metadata");
|
// Java
|
||||||
addEndpointToGroup("Java", "cert-sign");
|
addEndpointToGroup("Java", "merge-pdfs");
|
||||||
addEndpointToGroup("Java", "multi-page-layout");
|
addEndpointToGroup("Java", "remove-pages");
|
||||||
addEndpointToGroup("Java", "scale-pages");
|
addEndpointToGroup("Java", "split-pdfs");
|
||||||
addEndpointToGroup("Java", "add-page-numbers");
|
addEndpointToGroup("Java", "pdf-organizer");
|
||||||
addEndpointToGroup("Java", "auto-rename");
|
addEndpointToGroup("Java", "rotate-pdf");
|
||||||
addEndpointToGroup("Java", "auto-split-pdf");
|
addEndpointToGroup("Java", "pdf-to-img");
|
||||||
addEndpointToGroup("Java", "sanitize-pdf");
|
addEndpointToGroup("Java", "img-to-pdf");
|
||||||
addEndpointToGroup("Java", "crop");
|
addEndpointToGroup("Java", "add-password");
|
||||||
addEndpointToGroup("Java", "get-info-on-pdf");
|
addEndpointToGroup("Java", "remove-password");
|
||||||
addEndpointToGroup("Java", "extract-page");
|
addEndpointToGroup("Java", "change-permissions");
|
||||||
addEndpointToGroup("Java", "pdf-to-single-page");
|
addEndpointToGroup("Java", "add-watermark");
|
||||||
addEndpointToGroup("Java", "markdown-to-pdf");
|
addEndpointToGroup("Java", "add-image");
|
||||||
addEndpointToGroup("Java", "show-javascript");
|
addEndpointToGroup("Java", "extract-images");
|
||||||
addEndpointToGroup("Java", "auto-redact");
|
addEndpointToGroup("Java", "change-metadata");
|
||||||
addEndpointToGroup("Java", "pdf-to-csv");
|
addEndpointToGroup("Java", "cert-sign");
|
||||||
addEndpointToGroup("Java", "split-by-size-or-count");
|
addEndpointToGroup("Java", "multi-page-layout");
|
||||||
addEndpointToGroup("Java", "overlay-pdf");
|
addEndpointToGroup("Java", "scale-pages");
|
||||||
addEndpointToGroup("Java", "split-pdf-by-sections");
|
addEndpointToGroup("Java", "add-page-numbers");
|
||||||
|
addEndpointToGroup("Java", "auto-rename");
|
||||||
// Javascript
|
addEndpointToGroup("Java", "auto-split-pdf");
|
||||||
addEndpointToGroup("Javascript", "pdf-organizer");
|
addEndpointToGroup("Java", "sanitize-pdf");
|
||||||
addEndpointToGroup("Javascript", "sign");
|
addEndpointToGroup("Java", "crop");
|
||||||
addEndpointToGroup("Javascript", "compare");
|
addEndpointToGroup("Java", "get-info-on-pdf");
|
||||||
addEndpointToGroup("Javascript", "adjust-contrast");
|
addEndpointToGroup("Java", "extract-page");
|
||||||
}
|
addEndpointToGroup("Java", "pdf-to-single-page");
|
||||||
|
addEndpointToGroup("Java", "markdown-to-pdf");
|
||||||
private void processEnvironmentConfigs() {
|
addEndpointToGroup("Java", "show-javascript");
|
||||||
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
addEndpointToGroup("Java", "auto-redact");
|
||||||
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
addEndpointToGroup("Java", "pdf-to-csv");
|
||||||
|
addEndpointToGroup("Java", "split-by-size-or-count");
|
||||||
if (endpointsToRemove != null) {
|
addEndpointToGroup("Java", "overlay-pdf");
|
||||||
for (String endpoint : endpointsToRemove) {
|
addEndpointToGroup("Java", "split-pdf-by-sections");
|
||||||
disableEndpoint(endpoint.trim());
|
|
||||||
}
|
// Javascript
|
||||||
}
|
addEndpointToGroup("Javascript", "pdf-organizer");
|
||||||
|
addEndpointToGroup("Javascript", "sign");
|
||||||
if (groupsToRemove != null) {
|
addEndpointToGroup("Javascript", "compare");
|
||||||
for (String group : groupsToRemove) {
|
addEndpointToGroup("Javascript", "adjust-contrast");
|
||||||
disableGroup(group.trim());
|
}
|
||||||
}
|
|
||||||
}
|
private void processEnvironmentConfigs() {
|
||||||
}
|
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
||||||
}
|
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
||||||
|
if (!bookFormatsInstalled) {
|
||||||
|
groupsToRemove.add("Calibre");
|
||||||
|
}
|
||||||
|
if (endpointsToRemove != null) {
|
||||||
|
for (String endpoint : endpointsToRemove) {
|
||||||
|
disableEndpoint(endpoint.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupsToRemove != null) {
|
||||||
|
for (String group : groupsToRemove) {
|
||||||
|
disableGroup(group.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class EndpointInterceptor implements HandlerInterceptor {
|
public class EndpointInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
@Autowired private EndpointConfiguration endpointConfiguration;
|
@Autowired private EndpointConfiguration endpointConfiguration;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(
|
public boolean preHandle(
|
||||||
HttpServletRequest request, HttpServletResponse response, Object handler)
|
HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
if (!endpointConfiguration.isEndpointEnabled(requestURI)) {
|
if (!endpointConfiguration.isEndpointEnabled(requestURI)) {
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled");
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.Meter;
|
import io.micrometer.core.instrument.Meter;
|
||||||
import io.micrometer.core.instrument.config.MeterFilter;
|
import io.micrometer.core.instrument.config.MeterFilter;
|
||||||
import io.micrometer.core.instrument.config.MeterFilterReply;
|
import io.micrometer.core.instrument.config.MeterFilterReply;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class MetricsConfig {
|
public class MetricsConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public MeterFilter meterFilter() {
|
public MeterFilter meterFilter() {
|
||||||
return new MeterFilter() {
|
return new MeterFilter() {
|
||||||
@Override
|
@Override
|
||||||
public MeterFilterReply accept(Meter.Id id) {
|
public MeterFilterReply accept(Meter.Id id) {
|
||||||
if (id.getName().equals("http.requests")) {
|
if (id.getName().equals("http.requests")) {
|
||||||
return MeterFilterReply.NEUTRAL;
|
return MeterFilterReply.NEUTRAL;
|
||||||
}
|
}
|
||||||
return MeterFilterReply.DENY;
|
return MeterFilterReply.DENY;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,64 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.Counter;
|
import io.micrometer.core.instrument.Counter;
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MetricsFilter extends OncePerRequestFilter {
|
public class MetricsFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final MeterRegistry meterRegistry;
|
private final MeterRegistry meterRegistry;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public MetricsFilter(MeterRegistry meterRegistry) {
|
public MetricsFilter(MeterRegistry meterRegistry) {
|
||||||
this.meterRegistry = meterRegistry;
|
this.meterRegistry = meterRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(
|
protected void doFilterInternal(
|
||||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
|
|
||||||
// System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
// System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
||||||
// Ignore static resources
|
// Ignore static resources
|
||||||
if (!(uri.startsWith("/js")
|
if (!(uri.startsWith("/js")
|
||||||
|| uri.startsWith("/v1/api-docs")
|
|| uri.startsWith("/v1/api-docs")
|
||||||
|| uri.endsWith("robots.txt")
|
|| uri.endsWith("robots.txt")
|
||||||
|| uri.startsWith("/images")
|
|| uri.startsWith("/images")
|
||||||
|| uri.startsWith("/images")
|
|| uri.startsWith("/images")
|
||||||
|| uri.endsWith(".png")
|
|| uri.endsWith(".png")
|
||||||
|| uri.endsWith(".ico")
|
|| uri.endsWith(".ico")
|
||||||
|| uri.endsWith(".css")
|
|| uri.endsWith(".css")
|
||||||
|| uri.endsWith(".map")
|
|| uri.endsWith(".map")
|
||||||
|| uri.endsWith(".svg")
|
|| uri.endsWith(".svg")
|
||||||
|| uri.endsWith(".js")
|
|| uri.endsWith(".js")
|
||||||
|| uri.contains("swagger")
|
|| uri.contains("swagger")
|
||||||
|| uri.startsWith("/api/v1/info")
|
|| uri.startsWith("/api/v1/info")
|
||||||
|| uri.startsWith("/site.webmanifest")
|
|| uri.startsWith("/site.webmanifest")
|
||||||
|| uri.startsWith("/fonts")
|
|| uri.startsWith("/fonts")
|
||||||
|| uri.startsWith("/pdfjs"))) {
|
|| uri.startsWith("/pdfjs"))) {
|
||||||
|
|
||||||
Counter counter =
|
Counter counter =
|
||||||
Counter.builder("http.requests")
|
Counter.builder("http.requests")
|
||||||
.tag("uri", uri)
|
.tag("uri", uri)
|
||||||
.tag("method", request.getMethod())
|
.tag("method", request.getMethod())
|
||||||
.register(meterRegistry);
|
.register(meterRegistry);
|
||||||
|
|
||||||
counter.increment();
|
counter.increment();
|
||||||
// System.out.println("Counted");
|
// System.out.println("Counted");
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +1,53 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import io.swagger.v3.oas.models.Components;
|
import io.swagger.v3.oas.models.Components;
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class OpenApiConfig {
|
public class OpenApiConfig {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI customOpenAPI() {
|
public OpenAPI customOpenAPI() {
|
||||||
String version = getClass().getPackage().getImplementationVersion();
|
String version = getClass().getPackage().getImplementationVersion();
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
version = "1.0.0"; // default version if all else fails
|
version = "1.0.0"; // default version if all else fails
|
||||||
}
|
}
|
||||||
|
|
||||||
SecurityScheme apiKeyScheme =
|
SecurityScheme apiKeyScheme =
|
||||||
new SecurityScheme()
|
new SecurityScheme()
|
||||||
.type(SecurityScheme.Type.APIKEY)
|
.type(SecurityScheme.Type.APIKEY)
|
||||||
.in(SecurityScheme.In.HEADER)
|
.in(SecurityScheme.In.HEADER)
|
||||||
.name("X-API-KEY");
|
.name("X-API-KEY");
|
||||||
if (!applicationProperties.getSecurity().getEnableLogin()) {
|
if (!applicationProperties.getSecurity().getEnableLogin()) {
|
||||||
return new OpenAPI()
|
return new OpenAPI()
|
||||||
.components(new Components())
|
.components(new Components())
|
||||||
.info(
|
.info(
|
||||||
new Info()
|
new Info()
|
||||||
.title("Stirling PDF API")
|
.title("Stirling PDF API")
|
||||||
.version(version)
|
.version(version)
|
||||||
.description(
|
.description(
|
||||||
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
|
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."));
|
||||||
} else {
|
} else {
|
||||||
return new OpenAPI()
|
return new OpenAPI()
|
||||||
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
|
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
|
||||||
.info(
|
.info(
|
||||||
new Info()
|
new Info()
|
||||||
.title("Stirling PDF API")
|
.title("Stirling PDF API")
|
||||||
.version(version)
|
.version(version)
|
||||||
.description(
|
.description(
|
||||||
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."))
|
"API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here."))
|
||||||
.addSecurityItem(new SecurityRequirement().addList("apiKey"));
|
.addSecurityItem(new SecurityRequirement().addList("apiKey"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class PostStartupProcesses {
|
||||||
|
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("RunningInDocker")
|
||||||
|
private boolean runningInDocker;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("bookFormatsInstalled")
|
||||||
|
private boolean bookFormatsInstalled;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("htmlFormatsInstalled")
|
||||||
|
private boolean htmlFormatsInstalled;
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PostStartupProcesses.class);
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void runInstallCommandBasedOnEnvironment() throws IOException, InterruptedException {
|
||||||
|
List<List<String>> commands = new ArrayList<>();
|
||||||
|
// Checking for DOCKER_INSTALL_BOOK_FORMATS environment variable
|
||||||
|
if (bookFormatsInstalled) {
|
||||||
|
List<String> tmpList = new ArrayList<>();
|
||||||
|
// Set up the timezone configuration commands
|
||||||
|
tmpList.addAll(
|
||||||
|
Arrays.asList(
|
||||||
|
"sh",
|
||||||
|
"-c",
|
||||||
|
"echo 'tzdata tzdata/Areas select Europe' | debconf-set-selections; "
|
||||||
|
+ "echo 'tzdata tzdata/Zones/Europe select Berlin' | debconf-set-selections"));
|
||||||
|
commands.add(tmpList);
|
||||||
|
|
||||||
|
// Install calibre with DEBIAN_FRONTEND set to noninteractive
|
||||||
|
tmpList = new ArrayList<>();
|
||||||
|
tmpList.addAll(
|
||||||
|
Arrays.asList(
|
||||||
|
"sh",
|
||||||
|
"-c",
|
||||||
|
"DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends calibre"));
|
||||||
|
commands.add(tmpList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checking for DOCKER_INSTALL_HTML_FORMATS environment variable
|
||||||
|
if (htmlFormatsInstalled) {
|
||||||
|
List<String> tmpList = new ArrayList<>();
|
||||||
|
// Add -y flag for automatic yes to prompts and --no-install-recommends to reduce size
|
||||||
|
tmpList.addAll(
|
||||||
|
Arrays.asList(
|
||||||
|
"apt-get", "install", "wkhtmltopdf", "-y", "--no-install-recommends"));
|
||||||
|
commands.add(tmpList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!commands.isEmpty()) {
|
||||||
|
// Run the command
|
||||||
|
if (runningInDocker) {
|
||||||
|
List<String> tmpList = new ArrayList<>();
|
||||||
|
tmpList.addAll(Arrays.asList("apt-get", "update"));
|
||||||
|
commands.add(0, tmpList);
|
||||||
|
|
||||||
|
for (List<String> list : commands) {
|
||||||
|
ProcessExecutorResult returnCode =
|
||||||
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.INSTALL_APP, true)
|
||||||
|
.runCommandWithOutputHandling(list);
|
||||||
|
logger.info("RC for app installs {}", returnCode.getRc());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Not running inside Docker so skipping automated install process with command.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (runningInDocker) {
|
||||||
|
logger.info("No custom apps to install.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.event.ContextRefreshedEvent;
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
|
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
|
||||||
|
|
||||||
public static LocalDateTime startTime;
|
public static LocalDateTime startTime;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||||
startTime = LocalDateTime.now();
|
startTime = LocalDateTime.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class WebMvcConfig implements WebMvcConfigurer {
|
public class WebMvcConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired private EndpointInterceptor endpointInterceptor;
|
@Autowired private EndpointInterceptor endpointInterceptor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(endpointInterceptor);
|
registry.addInterceptor(endpointInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
// Handler for external static resources
|
// Handler for external static resources
|
||||||
registry.addResourceHandler("/**")
|
registry.addResourceHandler("/**")
|
||||||
.addResourceLocations("file:customFiles/static/", "classpath:/static/");
|
.addResourceLocations("file:customFiles/static/", "classpath:/static/");
|
||||||
// .setCachePeriod(0); // Optional: disable caching
|
// .setCachePeriod(0); // Optional: disable caching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,49 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
@Autowired private final LoginAttemptService loginAttemptService;
|
@Autowired private final LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) {
|
public CustomAuthenticationFailureHandler(LoginAttemptService loginAttemptService) {
|
||||||
this.loginAttemptService = loginAttemptService;
|
this.loginAttemptService = loginAttemptService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationFailure(
|
public void onAuthenticationFailure(
|
||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
AuthenticationException exception)
|
AuthenticationException exception)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
String ip = request.getRemoteAddr();
|
String ip = request.getRemoteAddr();
|
||||||
logger.error("Failed login attempt from IP: " + ip);
|
logger.error("Failed login attempt from IP: " + ip);
|
||||||
|
|
||||||
String username = request.getParameter("username");
|
String username = request.getParameter("username");
|
||||||
if (loginAttemptService.loginAttemptCheck(username)) {
|
if (loginAttemptService.loginAttemptCheck(username)) {
|
||||||
setDefaultFailureUrl("/login?error=locked");
|
setDefaultFailureUrl("/login?error=locked");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
|
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
|
||||||
setDefaultFailureUrl("/login?error=badcredentials");
|
setDefaultFailureUrl("/login?error=badcredentials");
|
||||||
} else if (exception.getClass().isAssignableFrom(LockedException.class)) {
|
} else if (exception.getClass().isAssignableFrom(LockedException.class)) {
|
||||||
setDefaultFailureUrl("/login?error=locked");
|
setDefaultFailureUrl("/login?error=locked");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onAuthenticationFailure(request, response, exception);
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,57 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.Authority;
|
import stirling.software.SPDF.model.Authority;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CustomUserDetailsService implements UserDetailsService {
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
@Autowired private UserRepository userRepository;
|
@Autowired private UserRepository userRepository;
|
||||||
|
|
||||||
@Autowired private LoginAttemptService loginAttemptService;
|
@Autowired private LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
User user =
|
User user =
|
||||||
userRepository
|
userRepository
|
||||||
.findByUsername(username)
|
.findByUsername(username)
|
||||||
.orElseThrow(
|
.orElseThrow(
|
||||||
() ->
|
() ->
|
||||||
new UsernameNotFoundException(
|
new UsernameNotFoundException(
|
||||||
"No user found with username: " + username));
|
"No user found with username: " + username));
|
||||||
|
|
||||||
if (loginAttemptService.isBlocked(username)) {
|
if (loginAttemptService.isBlocked(username)) {
|
||||||
throw new LockedException(
|
throw new LockedException(
|
||||||
"Your account has been locked due to too many failed login attempts.");
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new org.springframework.security.core.userdetails.User(
|
return new org.springframework.security.core.userdetails.User(
|
||||||
user.getUsername(),
|
user.getUsername(),
|
||||||
user.getPassword(),
|
user.getPassword(),
|
||||||
user.isEnabled(),
|
user.isEnabled(),
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
getAuthorities(user.getAuthorities()));
|
getAuthorities(user.getAuthorities()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<? extends GrantedAuthority> getAuthorities(Set<Authority> authorities) {
|
private Collection<? extends GrantedAuthority> getAuthorities(Set<Authority> authorities) {
|
||||||
return authorities.stream()
|
return authorities.stream()
|
||||||
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
|
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,137 +1,140 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity()
|
@EnableWebSecurity()
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
@Autowired private UserDetailsService userDetailsService;
|
@Autowired private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired @Lazy private UserService userService;
|
@Autowired @Lazy private UserService userService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("loginEnabled")
|
@Qualifier("loginEnabled")
|
||||||
public boolean loginEnabledValue;
|
public boolean loginEnabledValue;
|
||||||
|
|
||||||
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
|
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
|
||||||
|
|
||||||
@Autowired private LoginAttemptService loginAttemptService;
|
@Autowired private LoginAttemptService loginAttemptService;
|
||||||
|
|
||||||
@Autowired private FirstLoginFilter firstLoginFilter;
|
@Autowired private FirstLoginFilter firstLoginFilter;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
if (loginEnabledValue) {
|
if (loginEnabledValue) {
|
||||||
|
|
||||||
http.csrf(csrf -> csrf.disable());
|
http.csrf(csrf -> csrf.disable());
|
||||||
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
http.formLogin(
|
http.formLogin(
|
||||||
formLogin ->
|
formLogin ->
|
||||||
formLogin
|
formLogin
|
||||||
.loginPage("/login")
|
.loginPage("/login")
|
||||||
.successHandler(
|
.successHandler(
|
||||||
new CustomAuthenticationSuccessHandler())
|
new CustomAuthenticationSuccessHandler())
|
||||||
.defaultSuccessUrl("/")
|
.defaultSuccessUrl("/")
|
||||||
.failureHandler(
|
.failureHandler(
|
||||||
new CustomAuthenticationFailureHandler(
|
new CustomAuthenticationFailureHandler(
|
||||||
loginAttemptService))
|
loginAttemptService))
|
||||||
.permitAll())
|
.permitAll())
|
||||||
.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()))
|
.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()))
|
||||||
.logout(
|
.logout(
|
||||||
logout ->
|
logout ->
|
||||||
logout.logoutRequestMatcher(
|
logout.logoutRequestMatcher(
|
||||||
new AntPathRequestMatcher("/logout"))
|
new AntPathRequestMatcher("/logout"))
|
||||||
.logoutSuccessUrl("/login?logout=true")
|
.logoutSuccessUrl("/login?logout=true")
|
||||||
.invalidateHttpSession(true) // Invalidate session
|
.invalidateHttpSession(true) // Invalidate session
|
||||||
.deleteCookies("JSESSIONID", "remember-me"))
|
.deleteCookies("JSESSIONID", "remember-me"))
|
||||||
.rememberMe(
|
.rememberMe(
|
||||||
rememberMeConfigurer ->
|
rememberMeConfigurer ->
|
||||||
rememberMeConfigurer // Use the configurator directly
|
rememberMeConfigurer // Use the configurator directly
|
||||||
.key("uniqueAndSecret")
|
.key("uniqueAndSecret")
|
||||||
.tokenRepository(persistentTokenRepository())
|
.tokenRepository(persistentTokenRepository())
|
||||||
.tokenValiditySeconds(1209600) // 2 weeks
|
.tokenValiditySeconds(1209600) // 2 weeks
|
||||||
)
|
)
|
||||||
.authorizeHttpRequests(
|
.authorizeHttpRequests(
|
||||||
authz ->
|
authz ->
|
||||||
authz.requestMatchers(
|
authz.requestMatchers(
|
||||||
req -> {
|
req -> {
|
||||||
String uri = req.getRequestURI();
|
String uri = req.getRequestURI();
|
||||||
String contextPath = req.getContextPath();
|
String contextPath = req.getContextPath();
|
||||||
|
|
||||||
// Remove the context path from the URI
|
// Remove the context path from the URI
|
||||||
String trimmedUri =
|
String trimmedUri =
|
||||||
uri.startsWith(contextPath)
|
uri.startsWith(contextPath)
|
||||||
? uri.substring(
|
? uri.substring(
|
||||||
contextPath
|
contextPath
|
||||||
.length())
|
.length())
|
||||||
: uri;
|
: uri;
|
||||||
|
|
||||||
return trimmedUri.startsWith("/login")
|
return trimmedUri.startsWith("/login")
|
||||||
|| trimmedUri.endsWith(".svg")
|
|| trimmedUri.endsWith(".svg")
|
||||||
|| trimmedUri.startsWith(
|
|| trimmedUri.startsWith(
|
||||||
"/register")
|
"/register")
|
||||||
|| trimmedUri.startsWith("/error")
|
|| trimmedUri.startsWith("/error")
|
||||||
|| trimmedUri.startsWith("/images/")
|
|| trimmedUri.startsWith("/images/")
|
||||||
|| trimmedUri.startsWith("/public/")
|
|| trimmedUri.startsWith("/public/")
|
||||||
|| trimmedUri.startsWith("/css/")
|
|| trimmedUri.startsWith("/css/")
|
||||||
|| trimmedUri.startsWith("/js/");
|
|| trimmedUri.startsWith("/js/")
|
||||||
})
|
|| trimmedUri.startsWith(
|
||||||
.permitAll()
|
"/api/v1/info/status");
|
||||||
.anyRequest()
|
})
|
||||||
.authenticated())
|
.permitAll()
|
||||||
.userDetailsService(userDetailsService)
|
.anyRequest()
|
||||||
.authenticationProvider(authenticationProvider());
|
.authenticated())
|
||||||
} else {
|
.userDetailsService(userDetailsService)
|
||||||
http.csrf(csrf -> csrf.disable())
|
.authenticationProvider(authenticationProvider());
|
||||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
} else {
|
||||||
}
|
http.csrf(csrf -> csrf.disable())
|
||||||
return http.build();
|
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
return http.build();
|
||||||
public IPRateLimitingFilter rateLimitingFilter() {
|
}
|
||||||
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
|
||||||
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
|
@Bean
|
||||||
}
|
public IPRateLimitingFilter rateLimitingFilter() {
|
||||||
|
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
||||||
@Bean
|
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
|
||||||
public DaoAuthenticationProvider authenticationProvider() {
|
}
|
||||||
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
|
||||||
authProvider.setUserDetailsService(userDetailsService);
|
@Bean
|
||||||
authProvider.setPasswordEncoder(passwordEncoder());
|
public DaoAuthenticationProvider authenticationProvider() {
|
||||||
return authProvider;
|
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
}
|
authProvider.setUserDetailsService(userDetailsService);
|
||||||
|
authProvider.setPasswordEncoder(passwordEncoder());
|
||||||
@Bean
|
return authProvider;
|
||||||
public PersistentTokenRepository persistentTokenRepository() {
|
}
|
||||||
return new JPATokenRepositoryImpl();
|
|
||||||
}
|
@Bean
|
||||||
}
|
public PersistentTokenRepository persistentTokenRepository() {
|
||||||
|
return new JPATokenRepositoryImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,117 +1,118 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
@Autowired private UserDetailsService userDetailsService;
|
@Autowired private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
@Autowired @Lazy private UserService userService;
|
@Autowired @Lazy private UserService userService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("loginEnabled")
|
@Qualifier("loginEnabled")
|
||||||
public boolean loginEnabledValue;
|
public boolean loginEnabledValue;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(
|
protected void doFilterInternal(
|
||||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
|
|
||||||
if (!loginEnabledValue) {
|
if (!loginEnabledValue) {
|
||||||
// If login is not enabled, just pass all requests without authentication
|
// If login is not enabled, just pass all requests without authentication
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
// Check for API key in the request headers if no authentication exists
|
// Check for API key in the request headers if no authentication exists
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-Key");
|
||||||
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
// Use API key to authenticate. This requires you to have an authentication
|
// Use API key to authenticate. This requires you to have an authentication
|
||||||
// provider for API keys.
|
// provider for API keys.
|
||||||
UserDetails userDetails = userService.loadUserByApiKey(apiKey);
|
UserDetails userDetails = userService.loadUserByApiKey(apiKey);
|
||||||
if (userDetails == null) {
|
if (userDetails == null) {
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter().write("Invalid API Key.");
|
response.getWriter().write("Invalid API Key.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
authentication =
|
authentication =
|
||||||
new ApiKeyAuthenticationToken(
|
new ApiKeyAuthenticationToken(
|
||||||
userDetails, apiKey, userDetails.getAuthorities());
|
userDetails, apiKey, userDetails.getAuthorities());
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
} catch (AuthenticationException e) {
|
} catch (AuthenticationException e) {
|
||||||
// If API key authentication fails, deny the request
|
// If API key authentication fails, deny the request
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter().write("Invalid API Key.");
|
response.getWriter().write("Invalid API Key.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we still don't have any authentication, deny the request
|
// If we still don't have any authentication, deny the request
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
String method = request.getMethod();
|
String method = request.getMethod();
|
||||||
String contextPath = request.getContextPath();
|
String contextPath = request.getContextPath();
|
||||||
|
|
||||||
if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) {
|
if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) {
|
||||||
response.sendRedirect(contextPath + "/login"); // redirect to the login page
|
response.sendRedirect(contextPath + "/login"); // redirect to the login page
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter()
|
response.getWriter()
|
||||||
.write(
|
.write(
|
||||||
"Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected");
|
"Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
String contextPath = request.getContextPath();
|
String contextPath = request.getContextPath();
|
||||||
String[] permitAllPatterns = {
|
String[] permitAllPatterns = {
|
||||||
contextPath + "/login",
|
contextPath + "/login",
|
||||||
contextPath + "/register",
|
contextPath + "/register",
|
||||||
contextPath + "/error",
|
contextPath + "/error",
|
||||||
contextPath + "/images/",
|
contextPath + "/images/",
|
||||||
contextPath + "/public/",
|
contextPath + "/public/",
|
||||||
contextPath + "/css/",
|
contextPath + "/css/",
|
||||||
contextPath + "/js/",
|
contextPath + "/js/",
|
||||||
contextPath + "/pdfjs/",
|
contextPath + "/pdfjs/",
|
||||||
contextPath + "/site.webmanifest"
|
contextPath + "/api/v1/info/status",
|
||||||
};
|
contextPath + "/site.webmanifest"
|
||||||
|
};
|
||||||
for (String pattern : permitAllPatterns) {
|
|
||||||
if (uri.startsWith(pattern) || uri.endsWith(".svg")) {
|
for (String pattern : permitAllPatterns) {
|
||||||
return true;
|
if (uri.startsWith(pattern) || uri.endsWith(".svg")) {
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,143 +1,143 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import io.github.bucket4j.Bandwidth;
|
import io.github.bucket4j.Bandwidth;
|
||||||
import io.github.bucket4j.Bucket;
|
import io.github.bucket4j.Bucket;
|
||||||
import io.github.bucket4j.ConsumptionProbe;
|
import io.github.bucket4j.ConsumptionProbe;
|
||||||
import io.github.bucket4j.Refill;
|
import io.github.bucket4j.Refill;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>();
|
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>();
|
||||||
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>();
|
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Autowired private UserDetailsService userDetailsService;
|
@Autowired private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@Qualifier("rateLimit")
|
@Qualifier("rateLimit")
|
||||||
public boolean rateLimit;
|
public boolean rateLimit;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(
|
protected void doFilterInternal(
|
||||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
if (!rateLimit) {
|
if (!rateLimit) {
|
||||||
// If rateLimit is not enabled, just pass all requests without rate limiting
|
// If rateLimit is not enabled, just pass all requests without rate limiting
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String method = request.getMethod();
|
String method = request.getMethod();
|
||||||
if (!"POST".equalsIgnoreCase(method)) {
|
if (!"POST".equalsIgnoreCase(method)) {
|
||||||
// If the request is not a POST, just pass it through without rate limiting
|
// If the request is not a POST, just pass it through without rate limiting
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String identifier = null;
|
String identifier = null;
|
||||||
|
|
||||||
// Check for API key in the request headers
|
// Check for API key in the request headers
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-Key");
|
||||||
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||||
identifier =
|
identifier =
|
||||||
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
||||||
} else {
|
} else {
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
||||||
identifier = userDetails.getUsername();
|
identifier = userDetails.getUsername();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If neither API key nor an authenticated user is present, use IP address
|
// If neither API key nor an authenticated user is present, use IP address
|
||||||
if (identifier == null) {
|
if (identifier == null) {
|
||||||
identifier = request.getRemoteAddr();
|
identifier = request.getRemoteAddr();
|
||||||
}
|
}
|
||||||
|
|
||||||
Role userRole =
|
Role userRole =
|
||||||
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
||||||
|
|
||||||
if (request.getHeader("X-API-Key") != null) {
|
if (request.getHeader("X-API-Key") != null) {
|
||||||
// It's an API call
|
// It's an API call
|
||||||
processRequest(
|
processRequest(
|
||||||
userRole.getApiCallsPerDay(),
|
userRole.getApiCallsPerDay(),
|
||||||
identifier,
|
identifier,
|
||||||
apiBuckets,
|
apiBuckets,
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
filterChain);
|
filterChain);
|
||||||
} else {
|
} else {
|
||||||
// It's a Web UI call
|
// It's a Web UI call
|
||||||
processRequest(
|
processRequest(
|
||||||
userRole.getWebCallsPerDay(),
|
userRole.getWebCallsPerDay(),
|
||||||
identifier,
|
identifier,
|
||||||
webBuckets,
|
webBuckets,
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
filterChain);
|
filterChain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Role getRoleFromAuthentication(Authentication authentication) {
|
private Role getRoleFromAuthentication(Authentication authentication) {
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
for (GrantedAuthority authority : authentication.getAuthorities()) {
|
for (GrantedAuthority authority : authentication.getAuthorities()) {
|
||||||
try {
|
try {
|
||||||
return Role.fromString(authority.getAuthority());
|
return Role.fromString(authority.getAuthority());
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
// Ignore and continue to next authority.
|
// Ignore and continue to next authority.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("User does not have a valid role.");
|
throw new IllegalStateException("User does not have a valid role.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRequest(
|
private void processRequest(
|
||||||
int limitPerDay,
|
int limitPerDay,
|
||||||
String identifier,
|
String identifier,
|
||||||
Map<String, Bucket> buckets,
|
Map<String, Bucket> buckets,
|
||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
FilterChain filterChain)
|
FilterChain filterChain)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay));
|
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay));
|
||||||
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
|
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
|
||||||
|
|
||||||
if (probe.isConsumed()) {
|
if (probe.isConsumed()) {
|
||||||
response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens()));
|
response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens()));
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
} else {
|
} else {
|
||||||
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;
|
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;
|
||||||
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
|
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
|
||||||
response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill));
|
response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill));
|
||||||
response.getWriter().write("Rate limit exceeded for POST requests.");
|
response.getWriter().write("Rate limit exceeded for POST requests.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bucket createUserBucket(int limitPerDay) {
|
private Bucket createUserBucket(int limitPerDay) {
|
||||||
Bandwidth limit =
|
Bandwidth limit =
|
||||||
Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1)));
|
Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1)));
|
||||||
return Bucket.builder().addLimit(limit).build();
|
return Bucket.builder().addLimit(limit).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,197 +1,197 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.Authority;
|
import stirling.software.SPDF.model.Authority;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class UserService implements UserServiceInterface {
|
public class UserService implements UserServiceInterface {
|
||||||
|
|
||||||
@Autowired private UserRepository userRepository;
|
@Autowired private UserRepository userRepository;
|
||||||
|
|
||||||
@Autowired private PasswordEncoder passwordEncoder;
|
@Autowired private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public Authentication getAuthentication(String apiKey) {
|
public Authentication getAuthentication(String apiKey) {
|
||||||
User user = getUserByApiKey(apiKey);
|
User user = getUserByApiKey(apiKey);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new UsernameNotFoundException("API key is not valid");
|
throw new UsernameNotFoundException("API key is not valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the user into an Authentication object
|
// Convert the user into an Authentication object
|
||||||
return new UsernamePasswordAuthenticationToken(
|
return new UsernamePasswordAuthenticationToken(
|
||||||
user, // principal (typically the user)
|
user, // principal (typically the user)
|
||||||
null, // credentials (we don't expose the password or API key here)
|
null, // credentials (we don't expose the password or API key here)
|
||||||
getAuthorities(user) // user's authorities (roles/permissions)
|
getAuthorities(user) // user's authorities (roles/permissions)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
|
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
|
||||||
// Convert each Authority object into a SimpleGrantedAuthority object.
|
// Convert each Authority object into a SimpleGrantedAuthority object.
|
||||||
return user.getAuthorities().stream()
|
return user.getAuthorities().stream()
|
||||||
.map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority()))
|
.map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateApiKey() {
|
private String generateApiKey() {
|
||||||
String apiKey;
|
String apiKey;
|
||||||
do {
|
do {
|
||||||
apiKey = UUID.randomUUID().toString();
|
apiKey = UUID.randomUUID().toString();
|
||||||
} while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness
|
} while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness
|
||||||
return apiKey;
|
return apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User addApiKeyToUser(String username) {
|
public User addApiKeyToUser(String username) {
|
||||||
User user =
|
User user =
|
||||||
userRepository
|
userRepository
|
||||||
.findByUsername(username)
|
.findByUsername(username)
|
||||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||||
|
|
||||||
user.setApiKey(generateApiKey());
|
user.setApiKey(generateApiKey());
|
||||||
return userRepository.save(user);
|
return userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public User refreshApiKeyForUser(String username) {
|
public User refreshApiKeyForUser(String username) {
|
||||||
return addApiKeyToUser(username); // reuse the add API key method for refreshing
|
return addApiKeyToUser(username); // reuse the add API key method for refreshing
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getApiKeyForUser(String username) {
|
public String getApiKeyForUser(String username) {
|
||||||
User user =
|
User user =
|
||||||
userRepository
|
userRepository
|
||||||
.findByUsername(username)
|
.findByUsername(username)
|
||||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||||
return user.getApiKey();
|
return user.getApiKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValidApiKey(String apiKey) {
|
public boolean isValidApiKey(String apiKey) {
|
||||||
return userRepository.findByApiKey(apiKey) != null;
|
return userRepository.findByApiKey(apiKey) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User getUserByApiKey(String apiKey) {
|
public User getUserByApiKey(String apiKey) {
|
||||||
return userRepository.findByApiKey(apiKey);
|
return userRepository.findByApiKey(apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserDetails loadUserByApiKey(String apiKey) {
|
public UserDetails loadUserByApiKey(String apiKey) {
|
||||||
User userOptional = userRepository.findByApiKey(apiKey);
|
User userOptional = userRepository.findByApiKey(apiKey);
|
||||||
if (userOptional != null) {
|
if (userOptional != null) {
|
||||||
User user = userOptional;
|
User user = userOptional;
|
||||||
// Convert your User entity to a UserDetails object with authorities
|
// Convert your User entity to a UserDetails object with authorities
|
||||||
return new org.springframework.security.core.userdetails.User(
|
return new org.springframework.security.core.userdetails.User(
|
||||||
user.getUsername(),
|
user.getUsername(),
|
||||||
user.getPassword(), // you might not need this for API key auth
|
user.getPassword(), // you might not need this for API key auth
|
||||||
getAuthorities(user));
|
getAuthorities(user));
|
||||||
}
|
}
|
||||||
return null; // or throw an exception
|
return null; // or throw an exception
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validateApiKeyForUser(String username, String apiKey) {
|
public boolean validateApiKeyForUser(String username, String apiKey) {
|
||||||
Optional<User> userOpt = userRepository.findByUsername(username);
|
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||||
return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey);
|
return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveUser(String username, String password) {
|
public void saveUser(String username, String password) {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setUsername(username);
|
user.setUsername(username);
|
||||||
user.setPassword(passwordEncoder.encode(password));
|
user.setPassword(passwordEncoder.encode(password));
|
||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveUser(String username, String password, String role, boolean firstLogin) {
|
public void saveUser(String username, String password, String role, boolean firstLogin) {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setUsername(username);
|
user.setUsername(username);
|
||||||
user.setPassword(passwordEncoder.encode(password));
|
user.setPassword(passwordEncoder.encode(password));
|
||||||
user.addAuthority(new Authority(role, user));
|
user.addAuthority(new Authority(role, user));
|
||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
user.setFirstLogin(firstLogin);
|
user.setFirstLogin(firstLogin);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveUser(String username, String password, String role) {
|
public void saveUser(String username, String password, String role) {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setUsername(username);
|
user.setUsername(username);
|
||||||
user.setPassword(passwordEncoder.encode(password));
|
user.setPassword(passwordEncoder.encode(password));
|
||||||
user.addAuthority(new Authority(role, user));
|
user.addAuthority(new Authority(role, user));
|
||||||
user.setEnabled(true);
|
user.setEnabled(true);
|
||||||
user.setFirstLogin(false);
|
user.setFirstLogin(false);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteUser(String username) {
|
public void deleteUser(String username) {
|
||||||
Optional<User> userOpt = userRepository.findByUsername(username);
|
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
for (Authority authority : userOpt.get().getAuthorities()) {
|
for (Authority authority : userOpt.get().getAuthorities()) {
|
||||||
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
|
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userRepository.delete(userOpt.get());
|
userRepository.delete(userOpt.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean usernameExists(String username) {
|
public boolean usernameExists(String username) {
|
||||||
return userRepository.findByUsername(username).isPresent();
|
return userRepository.findByUsername(username).isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasUsers() {
|
public boolean hasUsers() {
|
||||||
return userRepository.count() > 0;
|
return userRepository.count() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateUserSettings(String username, Map<String, String> updates) {
|
public void updateUserSettings(String username, Map<String, String> updates) {
|
||||||
Optional<User> userOpt = userRepository.findByUsername(username);
|
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||||
if (userOpt.isPresent()) {
|
if (userOpt.isPresent()) {
|
||||||
User user = userOpt.get();
|
User user = userOpt.get();
|
||||||
Map<String, String> settingsMap = user.getSettings();
|
Map<String, String> settingsMap = user.getSettings();
|
||||||
|
|
||||||
if (settingsMap == null) {
|
if (settingsMap == null) {
|
||||||
settingsMap = new HashMap<String, String>();
|
settingsMap = new HashMap<String, String>();
|
||||||
}
|
}
|
||||||
settingsMap.clear();
|
settingsMap.clear();
|
||||||
settingsMap.putAll(updates);
|
settingsMap.putAll(updates);
|
||||||
user.setSettings(settingsMap);
|
user.setSettings(settingsMap);
|
||||||
|
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<User> findByUsername(String username) {
|
public Optional<User> findByUsername(String username) {
|
||||||
return userRepository.findByUsername(username);
|
return userRepository.findByUsername(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeUsername(User user, String newUsername) {
|
public void changeUsername(User user, String newUsername) {
|
||||||
user.setUsername(newUsername);
|
user.setUsername(newUsername);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changePassword(User user, String newPassword) {
|
public void changePassword(User user, String newPassword) {
|
||||||
user.setPassword(passwordEncoder.encode(newPassword));
|
user.setPassword(passwordEncoder.encode(newPassword));
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeFirstUse(User user, boolean firstUse) {
|
public void changeFirstUse(User user, boolean firstUse) {
|
||||||
user.setFirstLogin(firstUse);
|
user.setFirstLogin(firstUse);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPasswordCorrect(User user, String currentPassword) {
|
public boolean isPasswordCorrect(User user, String currentPassword) {
|
||||||
return passwordEncoder.matches(currentPassword, user.getPassword());
|
return passwordEncoder.matches(currentPassword, user.getPassword());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
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.multipdf.LayerUtility;
|
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -37,9 +38,7 @@ public class CropController {
|
|||||||
description =
|
description =
|
||||||
"This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")
|
"This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")
|
||||||
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 =
|
|
||||||
PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()));
|
|
||||||
|
|
||||||
PDDocument newDocument = new PDDocument();
|
PDDocument newDocument = new PDDocument();
|
||||||
|
|
||||||
@@ -53,7 +52,8 @@ public class CropController {
|
|||||||
// Create a new page with the size of the source page
|
// Create a new page with the size of the source page
|
||||||
PDPage newPage = new PDPage(sourcePage.getMediaBox());
|
PDPage newPage = new PDPage(sourcePage.getMediaBox());
|
||||||
newDocument.addPage(newPage);
|
newDocument.addPage(newPage);
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
|
PDPageContentStream contentStream =
|
||||||
|
new PDPageContentStream(newDocument, newPage, AppendMode.OVERWRITE, true, true);
|
||||||
|
|
||||||
// Import the source page as a form XObject
|
// Import the source page as a form XObject
|
||||||
PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);
|
PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
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.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.io.MemoryUsageSetting;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@@ -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.MergePdfsRequest;
|
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -84,8 +86,8 @@ public class MergeController {
|
|||||||
};
|
};
|
||||||
case "byPDFTitle":
|
case "byPDFTitle":
|
||||||
return (file1, file2) -> {
|
return (file1, file2) -> {
|
||||||
try (PDDocument doc1 = PDDocument.load(file1.getInputStream());
|
try (PDDocument doc1 = Loader.loadPDF(file1.getBytes());
|
||||||
PDDocument doc2 = PDDocument.load(file2.getInputStream())) {
|
PDDocument doc2 = Loader.loadPDF(file2.getBytes())) {
|
||||||
String title1 = doc1.getDocumentInformation().getTitle();
|
String title1 = doc1.getDocumentInformation().getTitle();
|
||||||
String title2 = doc2.getDocumentInformation().getTitle();
|
String title2 = doc2.getDocumentInformation().getTitle();
|
||||||
return title1.compareTo(title2);
|
return title1.compareTo(title2);
|
||||||
@@ -106,6 +108,7 @@ public class MergeController {
|
|||||||
"This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO")
|
"This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO")
|
||||||
public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form)
|
public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
List<File> filesToDelete = new ArrayList<File>();
|
||||||
try {
|
try {
|
||||||
MultipartFile[] files = form.getFileInput();
|
MultipartFile[] files = form.getFileInput();
|
||||||
Arrays.sort(files, getSortComparator(form.getSortType()));
|
Arrays.sort(files, getSortComparator(form.getSortType()));
|
||||||
@@ -113,20 +116,27 @@ public class MergeController {
|
|||||||
PDFMergerUtility mergedDoc = new PDFMergerUtility();
|
PDFMergerUtility mergedDoc = new PDFMergerUtility();
|
||||||
ByteArrayOutputStream docOutputstream = new ByteArrayOutputStream();
|
ByteArrayOutputStream docOutputstream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
for (MultipartFile file : files) {
|
for (MultipartFile multipartFile : files) {
|
||||||
mergedDoc.addSource(new ByteArrayInputStream(file.getBytes()));
|
File tempFile = GeneralUtils.convertMultipartFileToFile(multipartFile);
|
||||||
|
filesToDelete.add(tempFile);
|
||||||
|
mergedDoc.addSource(tempFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
mergedDoc.setDestinationFileName(
|
mergedDoc.setDestinationFileName(
|
||||||
files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
|
files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
|
||||||
mergedDoc.setDestinationStream(docOutputstream);
|
mergedDoc.setDestinationStream(docOutputstream);
|
||||||
mergedDoc.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
|
|
||||||
|
mergedDoc.mergeDocuments(null);
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
docOutputstream.toByteArray(), mergedDoc.getDestinationFileName());
|
docOutputstream.toByteArray(), mergedDoc.getDestinationFileName());
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("Error in merge pdf process", ex);
|
logger.error("Error in merge pdf process", ex);
|
||||||
throw ex;
|
throw ex;
|
||||||
|
} finally {
|
||||||
|
for (File file : filesToDelete) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.awt.Color;
|
|||||||
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.multipdf.LayerUtility;
|
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@@ -57,7 +58,7 @@ public class MultiPageLayoutController {
|
|||||||
: (int) Math.sqrt(pagesPerSheet);
|
: (int) Math.sqrt(pagesPerSheet);
|
||||||
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
|
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
|
||||||
|
|
||||||
PDDocument sourceDocument = PDDocument.load(file.getInputStream());
|
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
PDDocument newDocument = new PDDocument();
|
PDDocument newDocument = new PDDocument();
|
||||||
PDPage newPage = new PDPage(PDRectangle.A4);
|
PDPage newPage = new PDPage(PDRectangle.A4);
|
||||||
newDocument.addPage(newPage);
|
newDocument.addPage(newPage);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
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.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -53,7 +54,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 = PDDocument.load(baseFile.getInputStream());
|
try (PDDocument basePdf = Loader.loadPDF(baseFile.getBytes());
|
||||||
Overlay overlay = new Overlay()) {
|
Overlay overlay = new Overlay()) {
|
||||||
Map<Integer, String> overlayGuide =
|
Map<Integer, String> overlayGuide =
|
||||||
prepareOverlayGuide(
|
prepareOverlayGuide(
|
||||||
@@ -131,7 +132,7 @@ public class PdfOverlayController {
|
|||||||
overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length;
|
overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (PDDocument overlayPdf = PDDocument.load(overlayFiles[overlayFileIndex])) {
|
try (PDDocument overlayPdf = Loader.loadPDF(overlayFiles[overlayFileIndex])) {
|
||||||
PDDocument singlePageDocument = new PDDocument();
|
PDDocument singlePageDocument = new PDDocument();
|
||||||
singlePageDocument.addPage(overlayPdf.getPage(pageCountInCurrentOverlay));
|
singlePageDocument.addPage(overlayPdf.getPage(pageCountInCurrentOverlay));
|
||||||
File tempFile = File.createTempFile("overlay-page-", ".pdf");
|
File tempFile = File.createTempFile("overlay-page-", ".pdf");
|
||||||
@@ -147,7 +148,7 @@ public class PdfOverlayController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int getNumberOfPages(File file) throws IOException {
|
private int getNumberOfPages(File file) throws IOException {
|
||||||
try (PDDocument doc = PDDocument.load(file)) {
|
try (PDDocument doc = Loader.loadPDF(file)) {
|
||||||
return doc.getNumberOfPages();
|
return doc.getNumberOfPages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +160,7 @@ public class PdfOverlayController {
|
|||||||
File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length];
|
File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length];
|
||||||
|
|
||||||
// Load the overlay document to check its page count
|
// Load the overlay document to check its page count
|
||||||
try (PDDocument overlayPdf = PDDocument.load(overlayFile)) {
|
try (PDDocument overlayPdf = Loader.loadPDF(overlayFile)) {
|
||||||
int overlayPageCount = overlayPdf.getNumberOfPages();
|
int overlayPageCount = overlayPdf.getNumberOfPages();
|
||||||
if ((basePageIndex - 1) % overlayPageCount < overlayPageCount) {
|
if ((basePageIndex - 1) % overlayPageCount < overlayPageCount) {
|
||||||
overlayGuide.put(basePageIndex, overlayFile.getAbsolutePath());
|
overlayGuide.put(basePageIndex, overlayFile.getAbsolutePath());
|
||||||
@@ -181,7 +182,7 @@ public class PdfOverlayController {
|
|||||||
int repeatCount = counts[i];
|
int repeatCount = counts[i];
|
||||||
|
|
||||||
// Load the overlay document to check its page count
|
// Load the overlay document to check its page count
|
||||||
try (PDDocument overlayPdf = PDDocument.load(overlayFile)) {
|
try (PDDocument overlayPdf = Loader.loadPDF(overlayFile)) {
|
||||||
int overlayPageCount = overlayPdf.getNumberOfPages();
|
int overlayPageCount = overlayPdf.getNumberOfPages();
|
||||||
for (int j = 0; j < repeatCount; j++) {
|
for (int j = 0; j < repeatCount; j++) {
|
||||||
for (int page = 0; page < overlayPageCount; page++) {
|
for (int page = 0; page < overlayPageCount; page++) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
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.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -42,7 +43,7 @@ public class RearrangePagesPDFController {
|
|||||||
MultipartFile pdfFile = request.getFileInput();
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
String pagesToDelete = request.getPageNumbers();
|
String pagesToDelete = request.getPageNumbers();
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||||
|
|
||||||
// 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(",");
|
||||||
@@ -179,7 +180,7 @@ public class RearrangePagesPDFController {
|
|||||||
String sortType = request.getCustomMode();
|
String sortType = request.getCustomMode();
|
||||||
try {
|
try {
|
||||||
// Load the input PDF
|
// Load the input PDF
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||||
|
|
||||||
// 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 = pageOrder != null ? pageOrder.split(",") : new String[0];
|
String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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;
|
||||||
@@ -37,7 +38,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 = PDDocument.load(pdfFile.getBytes());
|
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||||
|
|
||||||
// Get the list of pages in the document
|
// Get the list of pages in the document
|
||||||
PDPageTree pages = document.getPages();
|
PDPageTree pages = document.getPages();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.io.IOException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.multipdf.LayerUtility;
|
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@@ -66,7 +67,7 @@ public class ScalePagesController {
|
|||||||
|
|
||||||
PDRectangle targetSize = sizeMap.get(targetPDRectangle);
|
PDRectangle targetSize = sizeMap.get(targetPDRectangle);
|
||||||
|
|
||||||
PDDocument sourceDocument = PDDocument.load(file.getBytes());
|
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
PDDocument outputDocument = new PDDocument();
|
PDDocument outputDocument = new PDDocument();
|
||||||
|
|
||||||
int totalPages = sourceDocument.getNumberOfPages();
|
int totalPages = sourceDocument.getNumberOfPages();
|
||||||
@@ -83,7 +84,11 @@ public class ScalePagesController {
|
|||||||
|
|
||||||
PDPageContentStream contentStream =
|
PDPageContentStream contentStream =
|
||||||
new PDPageContentStream(
|
new PDPageContentStream(
|
||||||
outputDocument, newPage, PDPageContentStream.AppendMode.APPEND, true);
|
outputDocument,
|
||||||
|
newPage,
|
||||||
|
PDPageContentStream.AppendMode.APPEND,
|
||||||
|
true,
|
||||||
|
true);
|
||||||
|
|
||||||
float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2;
|
float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2;
|
||||||
float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2;
|
float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api;
|
|||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
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;
|
||||||
@@ -11,6 +10,7 @@ import java.util.stream.Collectors;
|
|||||||
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.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -46,8 +46,8 @@ public class SplitPDFController {
|
|||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
String pages = request.getPageNumbers();
|
String pages = request.getPageNumbers();
|
||||||
// open the pdf document
|
// open the pdf document
|
||||||
InputStream inputStream = file.getInputStream();
|
|
||||||
PDDocument document = PDDocument.load(inputStream);
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
|
|
||||||
List<Integer> pageNumbers = request.getPageNumbersList(document);
|
List<Integer> pageNumbers = request.getPageNumbersList(document);
|
||||||
if (!pageNumbers.contains(document.getNumberOfPages() - 1))
|
if (!pageNumbers.contains(document.getNumberOfPages() - 1))
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ 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.multipdf.LayerUtility;
|
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
@@ -45,7 +47,7 @@ public class SplitPdfBySectionsController {
|
|||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
|
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
PDDocument sourceDocument = PDDocument.load(file.getInputStream());
|
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
|
|
||||||
// Process the PDF based on split parameters
|
// Process the PDF based on split parameters
|
||||||
int horiz = request.getHorizontalDivisions() + 1;
|
int horiz = request.getHorizontalDivisions() + 1;
|
||||||
@@ -115,10 +117,13 @@ public class SplitPdfBySectionsController {
|
|||||||
document, document.getPages().indexOf(originalPage));
|
document, document.getPages().indexOf(originalPage));
|
||||||
|
|
||||||
try (PDPageContentStream contentStream =
|
try (PDPageContentStream contentStream =
|
||||||
new PDPageContentStream(subDoc, subPage)) {
|
new PDPageContentStream(
|
||||||
|
subDoc, subPage, AppendMode.APPEND, true, true)) {
|
||||||
// Set clipping area and position
|
// Set clipping area and position
|
||||||
float translateX = -subPageWidth * i;
|
float translateX = -subPageWidth * i;
|
||||||
float translateY = height - subPageHeight * (verticalDivisions - j);
|
|
||||||
|
// float translateY = height - subPageHeight * (verticalDivisions - j);
|
||||||
|
float translateY = -subPageHeight * (verticalDivisions - 1 - j);
|
||||||
|
|
||||||
contentStream.saveGraphicsState();
|
contentStream.saveGraphicsState();
|
||||||
contentStream.addRect(0, 0, subPageWidth, subPageHeight);
|
contentStream.addRect(0, 0, subPageWidth, subPageHeight);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -42,7 +43,7 @@ public class SplitPdfBySizeController {
|
|||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();
|
||||||
|
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
PDDocument sourceDocument = PDDocument.load(file.getInputStream());
|
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
|
|
||||||
// 0 = size, 1 = page count, 2 = doc count
|
// 0 = size, 1 = page count, 2 = doc count
|
||||||
int type = request.getSplitType();
|
int type = request.getSplitType();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.awt.geom.AffineTransform;
|
|||||||
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.multipdf.LayerUtility;
|
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@@ -40,7 +41,7 @@ public class ToSinglePageController {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
// Load the source document
|
// Load the source document
|
||||||
PDDocument sourceDocument = PDDocument.load(request.getFileInput().getInputStream());
|
PDDocument sourceDocument = Loader.loadPDF(request.getFileInput().getBytes());
|
||||||
|
|
||||||
// Calculate total height and max width
|
// Calculate total height and max width
|
||||||
float totalHeight = 0;
|
float totalHeight = 0;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -20,13 +21,18 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||||
import org.springframework.web.servlet.view.RedirectView;
|
import org.springframework.web.servlet.view.RedirectView;
|
||||||
|
|
||||||
|
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 stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
import stirling.software.SPDF.model.api.user.UpdateUserDetails;
|
||||||
|
import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
|
@Tag(name = "User", description = "User APIs")
|
||||||
@RequestMapping("/api/v1/user")
|
@RequestMapping("/api/v1/user")
|
||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
@@ -34,14 +40,13 @@ public class UserController {
|
|||||||
|
|
||||||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public String register(
|
public String register(@ModelAttribute UsernameAndPass requestModel, Model model) {
|
||||||
@RequestParam String username, @RequestParam String password, Model model) {
|
if (userService.usernameExists(requestModel.getUsername())) {
|
||||||
if (userService.usernameExists(username)) {
|
|
||||||
model.addAttribute("error", "Username already exists");
|
model.addAttribute("error", "Username already exists");
|
||||||
return "register";
|
return "register";
|
||||||
}
|
}
|
||||||
|
|
||||||
userService.saveUser(username, password);
|
userService.saveUser(requestModel.getUsername(), requestModel.getPassword());
|
||||||
return "redirect:/login?registered=true";
|
return "redirect:/login?registered=true";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,12 +54,15 @@ public class UserController {
|
|||||||
@PostMapping("/change-username-and-password")
|
@PostMapping("/change-username-and-password")
|
||||||
public RedirectView changeUsernameAndPassword(
|
public RedirectView changeUsernameAndPassword(
|
||||||
Principal principal,
|
Principal principal,
|
||||||
@RequestParam String currentPassword,
|
@ModelAttribute UpdateUserDetails requestModel,
|
||||||
@RequestParam String newUsername,
|
|
||||||
@RequestParam String newPassword,
|
|
||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
RedirectAttributes redirectAttributes) {
|
RedirectAttributes redirectAttributes) {
|
||||||
|
|
||||||
|
String currentPassword = requestModel.getPassword();
|
||||||
|
String newPassword = requestModel.getNewPassword();
|
||||||
|
String newUsername = requestModel.getNewUsername();
|
||||||
|
|
||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
return new RedirectView("/change-creds?messageType=notAuthenticated");
|
return new RedirectView("/change-creds?messageType=notAuthenticated");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.GeneralFile;
|
||||||
|
import stirling.software.SPDF.utils.FileToPdf;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
|
public class ConvertBookToPDFController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("bookFormatsInstalled")
|
||||||
|
private boolean bookFormatsInstalled;
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
|
||||||
|
@Operation(
|
||||||
|
summary =
|
||||||
|
"Convert a BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) to PDF",
|
||||||
|
description =
|
||||||
|
"(Requires bookFormatsInstalled flag and Calibre installed) This endpoint takes an BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) input and converts it to PDF format.")
|
||||||
|
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception {
|
||||||
|
MultipartFile fileInput = request.getFileInput();
|
||||||
|
|
||||||
|
if (!bookFormatsInstalled) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"bookFormatsInstalled flag is False, this functionality is not avaiable");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileInput == null) {
|
||||||
|
throw new IllegalArgumentException("Please provide a file for conversion.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String originalFilename = fileInput.getOriginalFilename();
|
||||||
|
|
||||||
|
if (originalFilename != null) {
|
||||||
|
String originalFilenameLower = originalFilename.toLowerCase();
|
||||||
|
if (!originalFilenameLower.endsWith(".epub")
|
||||||
|
&& !originalFilenameLower.endsWith(".mobi")
|
||||||
|
&& !originalFilenameLower.endsWith(".azw3")
|
||||||
|
&& !originalFilenameLower.endsWith(".fb2")
|
||||||
|
&& !originalFilenameLower.endsWith(".txt")
|
||||||
|
&& !originalFilenameLower.endsWith(".docx")) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"File must be in .epub, .mobi, .azw3, .fb2, .txt, or .docx format.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte[] pdfBytes = FileToPdf.convertBookTypeToPdf(fileInput.getBytes(), originalFilename);
|
||||||
|
|
||||||
|
String outputFilename =
|
||||||
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
|
+ ".pdf"; // Remove file extension and append .pdf
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.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;
|
||||||
@@ -10,7 +12,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.GeneralFile;
|
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
|
||||||
import stirling.software.SPDF.utils.FileToPdf;
|
import stirling.software.SPDF.utils.FileToPdf;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@@ -19,12 +21,17 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertHtmlToPDF {
|
public class ConvertHtmlToPDF {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("htmlFormatsInstalled")
|
||||||
|
private boolean htmlFormatsInstalled;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
|
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
|
||||||
description =
|
description =
|
||||||
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format.")
|
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format.")
|
||||||
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception {
|
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request)
|
||||||
|
throws Exception {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
|
|
||||||
if (fileInput == null) {
|
if (fileInput == null) {
|
||||||
@@ -37,7 +44,9 @@ public class ConvertHtmlToPDF {
|
|||||||
|| (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) {
|
|| (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) {
|
||||||
throw new IllegalArgumentException("File must be either .html or .zip format.");
|
throw new IllegalArgumentException("File must be either .html or .zip format.");
|
||||||
}
|
}
|
||||||
byte[] pdfBytes = FileToPdf.convertHtmlToPdf(fileInput.getBytes(), originalFilename);
|
byte[] pdfBytes =
|
||||||
|
FileToPdf.convertHtmlToPdf(
|
||||||
|
request, fileInput.getBytes(), originalFilename, htmlFormatsInstalled);
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class ConvertImgPDFController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert images to a PDF file",
|
summary = "Convert images to a PDF file",
|
||||||
description =
|
description =
|
||||||
"This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?")
|
"This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:MISO")
|
||||||
public ResponseEntity<byte[]> convertToPdf(@ModelAttribute ConvertToPdfRequest request)
|
public ResponseEntity<byte[]> convertToPdf(@ModelAttribute ConvertToPdfRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile[] file = request.getFileInput();
|
MultipartFile[] file = request.getFileInput();
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.commonmark.Extension;
|
||||||
|
import org.commonmark.ext.gfm.tables.TableBlock;
|
||||||
|
import org.commonmark.ext.gfm.tables.TablesExtension;
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.parser.Parser;
|
import org.commonmark.parser.Parser;
|
||||||
|
import org.commonmark.renderer.html.AttributeProvider;
|
||||||
import org.commonmark.renderer.html.HtmlRenderer;
|
import org.commonmark.renderer.html.HtmlRenderer;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.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;
|
||||||
@@ -22,11 +31,15 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertMarkdownToPdf {
|
public class ConvertMarkdownToPdf {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("htmlFormatsInstalled")
|
||||||
|
private boolean htmlFormatsInstalled;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a Markdown file to PDF",
|
summary = "Convert a Markdown file to PDF",
|
||||||
description =
|
description =
|
||||||
"This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format.")
|
"This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format. Input:MARKDOWN Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile request)
|
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
@@ -41,12 +54,21 @@ public class ConvertMarkdownToPdf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert Markdown to HTML using CommonMark
|
// Convert Markdown to HTML using CommonMark
|
||||||
Parser parser = Parser.builder().build();
|
List<Extension> extensions = List.of(TablesExtension.create());
|
||||||
|
Parser parser = Parser.builder().extensions(extensions).build();
|
||||||
|
|
||||||
Node document = parser.parse(new String(fileInput.getBytes()));
|
Node document = parser.parse(new String(fileInput.getBytes()));
|
||||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
HtmlRenderer renderer =
|
||||||
|
HtmlRenderer.builder()
|
||||||
|
.attributeProviderFactory(context -> new TableAttributeProvider())
|
||||||
|
.extensions(extensions)
|
||||||
|
.build();
|
||||||
|
|
||||||
String htmlContent = renderer.render(document);
|
String htmlContent = renderer.render(document);
|
||||||
|
|
||||||
byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html");
|
byte[] pdfBytes =
|
||||||
|
FileToPdf.convertHtmlToPdf(
|
||||||
|
null, htmlContent.getBytes(), "converted.html", htmlFormatsInstalled);
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
@@ -54,3 +76,12 @@ public class ConvertMarkdownToPdf {
|
|||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TableAttributeProvider implements AttributeProvider {
|
||||||
|
@Override
|
||||||
|
public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
|
||||||
|
if (node instanceof TableBlock) {
|
||||||
|
attributes.put("class", "table table-striped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class ConvertOfficeController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a file to a PDF using LibreOffice",
|
summary = "Convert a file to a PDF using LibreOffice",
|
||||||
description =
|
description =
|
||||||
"This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO")
|
"This endpoint converts a given file to a PDF using LibreOffice API Input:ANY Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> processFileToPDF(@ModelAttribute GeneralFile request)
|
public ResponseEntity<byte[]> processFileToPDF(@ModelAttribute GeneralFile request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.converters.PdfToBookRequest;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
|
public class ConvertPDFToBookController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("bookFormatsInstalled")
|
||||||
|
private boolean bookFormatsInstalled;
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/book")
|
||||||
|
@Operation(
|
||||||
|
summary =
|
||||||
|
"Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF",
|
||||||
|
description =
|
||||||
|
"(Requires bookFormatsInstalled flag and Calibre installed) This endpoint Convert a PDF to a Book/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx .. (others to include by chatgpt) to PDF")
|
||||||
|
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute PdfToBookRequest request)
|
||||||
|
throws Exception {
|
||||||
|
MultipartFile fileInput = request.getFileInput();
|
||||||
|
|
||||||
|
if (!bookFormatsInstalled) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"bookFormatsInstalled flag is False, this functionality is not avaiable");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileInput == null) {
|
||||||
|
throw new IllegalArgumentException("Please provide a file for conversion.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the output format
|
||||||
|
String outputFormat = request.getOutputFormat().toLowerCase();
|
||||||
|
List<String> allowedFormats =
|
||||||
|
Arrays.asList(
|
||||||
|
"epub", "mobi", "azw3", "docx", "rtf", "txt", "html", "lit", "fb2", "pdb",
|
||||||
|
"lrf");
|
||||||
|
if (!allowedFormats.contains(outputFormat)) {
|
||||||
|
throw new IllegalArgumentException("Invalid output format: " + outputFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] outputFileBytes;
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
Path tempOutputFile =
|
||||||
|
Files.createTempFile(
|
||||||
|
"output_",
|
||||||
|
"." + outputFormat); // Use the output format for the file extension
|
||||||
|
Path tempInputFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create temp input file from the provided PDF
|
||||||
|
tempInputFile = Files.createTempFile("input_", ".pdf"); // Assuming input is always PDF
|
||||||
|
Files.write(tempInputFile, fileInput.getBytes());
|
||||||
|
|
||||||
|
command.add("ebook-convert");
|
||||||
|
command.add(tempInputFile.toString());
|
||||||
|
command.add(tempOutputFile.toString());
|
||||||
|
|
||||||
|
ProcessExecutorResult returnCode =
|
||||||
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
|
outputFileBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
} finally {
|
||||||
|
// Clean up temporary files
|
||||||
|
if (tempInputFile != null) {
|
||||||
|
Files.deleteIfExists(tempInputFile);
|
||||||
|
}
|
||||||
|
Files.deleteIfExists(tempOutputFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
String outputFilename =
|
||||||
|
fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
||||||
|
+ "."
|
||||||
|
+ outputFormat; // Remove file extension and append .pdf
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(outputFileBytes, outputFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,77 +1,87 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
@RestController
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
|
||||||
@RequestMapping("/api/v1/convert")
|
@RestController
|
||||||
public class ConvertWebsiteToPDF {
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
public class ConvertWebsiteToPDF {
|
||||||
@Operation(
|
|
||||||
summary = "Convert a URL to a PDF",
|
@Autowired
|
||||||
description =
|
@Qualifier("htmlFormatsInstalled")
|
||||||
"This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO")
|
private boolean htmlFormatsInstalled;
|
||||||
public ResponseEntity<byte[]> urlToPdf(@ModelAttribute UrlToPdfRequest request)
|
|
||||||
throws IOException, InterruptedException {
|
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
||||||
String URL = request.getUrlInput();
|
@Operation(
|
||||||
|
summary = "Convert a URL to a PDF",
|
||||||
// Validate the URL format
|
description =
|
||||||
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
|
"This endpoint fetches content from a URL and converts it to a PDF format. Input:N/A Output:PDF Type:SISO")
|
||||||
throw new IllegalArgumentException("Invalid URL format provided.");
|
public ResponseEntity<byte[]> urlToPdf(@ModelAttribute UrlToPdfRequest request)
|
||||||
}
|
throws IOException, InterruptedException {
|
||||||
Path tempOutputFile = null;
|
String URL = request.getUrlInput();
|
||||||
byte[] pdfBytes;
|
|
||||||
try {
|
// Validate the URL format
|
||||||
// Prepare the output file path
|
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
|
||||||
tempOutputFile = Files.createTempFile("output_", ".pdf");
|
throw new IllegalArgumentException("Invalid URL format provided.");
|
||||||
|
}
|
||||||
// Prepare the OCRmyPDF command
|
Path tempOutputFile = null;
|
||||||
List<String> command = new ArrayList<>();
|
byte[] pdfBytes;
|
||||||
command.add("weasyprint");
|
try {
|
||||||
command.add(URL);
|
// Prepare the output file path
|
||||||
command.add(tempOutputFile.toString());
|
tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
|
||||||
ProcessExecutorResult returnCode =
|
// Prepare the OCRmyPDF command
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
List<String> command = new ArrayList<>();
|
||||||
.runCommandWithOutputHandling(command);
|
if (!htmlFormatsInstalled) {
|
||||||
|
command.add("weasyprint");
|
||||||
// Read the optimized PDF file
|
} else {
|
||||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
command.add("wkhtmltopdf");
|
||||||
} finally {
|
}
|
||||||
// Clean up the temporary files
|
command.add(URL);
|
||||||
Files.delete(tempOutputFile);
|
command.add(tempOutputFile.toString());
|
||||||
}
|
|
||||||
// Convert URL to a safe filename
|
ProcessExecutorResult returnCode =
|
||||||
String outputFilename = convertURLToFileName(URL);
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
|
||||||
}
|
// Read the optimized PDF file
|
||||||
|
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
private String convertURLToFileName(String url) {
|
} finally {
|
||||||
String safeName = url.replaceAll("[^a-zA-Z0-9]", "_");
|
// Clean up the temporary files
|
||||||
if (safeName.length() > 50) {
|
Files.delete(tempOutputFile);
|
||||||
safeName = safeName.substring(0, 50); // restrict to 50 characters
|
}
|
||||||
}
|
// Convert URL to a safe filename
|
||||||
return safeName + ".pdf";
|
String outputFilename = convertURLToFileName(URL);
|
||||||
}
|
|
||||||
}
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String convertURLToFileName(String url) {
|
||||||
|
String safeName = url.replaceAll("[^a-zA-Z0-9]", "_");
|
||||||
|
if (safeName.length() > 50) {
|
||||||
|
safeName = safeName.substring(0, 50); // restrict to 50 characters
|
||||||
|
}
|
||||||
|
return safeName + ".pdf";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
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.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -44,8 +44,7 @@ public class ExtractController {
|
|||||||
ArrayList<String> tableData = new ArrayList<>();
|
ArrayList<String> tableData = new ArrayList<>();
|
||||||
int columnsCount = 0;
|
int columnsCount = 0;
|
||||||
|
|
||||||
try (PDDocument document =
|
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
|
||||||
PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
|
|
||||||
final double res = 72; // PDF units are at 72 DPI
|
final double res = 72; // PDF units are at 72 DPI
|
||||||
PDFTableStripper stripper = new PDFTableStripper();
|
PDFTableStripper stripper = new PDFTableStripper();
|
||||||
PDPage pdPage = document.getPage(form.getPageId() - 1);
|
PDPage pdPage = document.getPage(form.getPageId() - 1);
|
||||||
|
|||||||
@@ -1,210 +1,211 @@
|
|||||||
package stirling.software.SPDF.controller.api.filters;
|
package stirling.software.SPDF.controller.api.filters;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.model.api.PDFComparisonAndCount;
|
|
||||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
import stirling.software.SPDF.model.api.PDFComparisonAndCount;
|
||||||
import stirling.software.SPDF.model.api.filter.ContainsTextRequest;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
import stirling.software.SPDF.model.api.filter.FileSizeRequest;
|
import stirling.software.SPDF.model.api.filter.ContainsTextRequest;
|
||||||
import stirling.software.SPDF.model.api.filter.PageRotationRequest;
|
import stirling.software.SPDF.model.api.filter.FileSizeRequest;
|
||||||
import stirling.software.SPDF.model.api.filter.PageSizeRequest;
|
import stirling.software.SPDF.model.api.filter.PageRotationRequest;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.model.api.filter.PageSizeRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/filter")
|
@RestController
|
||||||
@Tag(name = "Filter", description = "Filter APIs")
|
@RequestMapping("/api/v1/filter")
|
||||||
public class FilterController {
|
@Tag(name = "Filter", description = "Filter APIs")
|
||||||
|
public class FilterController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
|
|
||||||
@Operation(
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
|
||||||
summary = "Checks if a PDF contains set text, returns true if does",
|
@Operation(
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
summary = "Checks if a PDF contains set text, returns true if does",
|
||||||
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request)
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
throws IOException, InterruptedException {
|
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request)
|
||||||
MultipartFile inputFile = request.getFileInput();
|
throws IOException, InterruptedException {
|
||||||
String text = request.getText();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String pageNumber = request.getPageNumbers();
|
String text = request.getText();
|
||||||
|
String pageNumber = request.getPageNumbers();
|
||||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
|
||||||
if (PdfUtils.hasText(pdfDocument, pageNumber, text))
|
PDDocument pdfDocument = Loader.loadPDF(inputFile.getBytes());
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
if (PdfUtils.hasText(pdfDocument, pageNumber, text))
|
||||||
pdfDocument, inputFile.getOriginalFilename());
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
return null;
|
pdfDocument, inputFile.getOriginalFilename());
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
// TODO
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
|
// TODO
|
||||||
@Operation(
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
|
||||||
summary = "Checks if a PDF contains an image",
|
@Operation(
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
summary = "Checks if a PDF contains an image",
|
||||||
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request)
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
throws IOException, InterruptedException {
|
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request)
|
||||||
MultipartFile inputFile = request.getFileInput();
|
throws IOException, InterruptedException {
|
||||||
String pageNumber = request.getPageNumbers();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
|
String pageNumber = request.getPageNumbers();
|
||||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
|
||||||
if (PdfUtils.hasImages(pdfDocument, pageNumber))
|
PDDocument pdfDocument = Loader.loadPDF(inputFile.getBytes());
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
if (PdfUtils.hasImages(pdfDocument, pageNumber))
|
||||||
pdfDocument, inputFile.getOriginalFilename());
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
return null;
|
pdfDocument, inputFile.getOriginalFilename());
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
|
|
||||||
@Operation(
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
|
||||||
summary = "Checks if a PDF is greater, less or equal to a setPageCount",
|
@Operation(
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
summary = "Checks if a PDF is greater, less or equal to a setPageCount",
|
||||||
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request)
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
throws IOException, InterruptedException {
|
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request)
|
||||||
MultipartFile inputFile = request.getFileInput();
|
throws IOException, InterruptedException {
|
||||||
String pageCount = request.getPageCount();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String comparator = request.getComparator();
|
String pageCount = request.getPageCount();
|
||||||
// Load the PDF
|
String comparator = request.getComparator();
|
||||||
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
// Load the PDF
|
||||||
int actualPageCount = document.getNumberOfPages();
|
PDDocument document = Loader.loadPDF(inputFile.getBytes());
|
||||||
|
int actualPageCount = document.getNumberOfPages();
|
||||||
boolean valid = false;
|
|
||||||
// Perform the comparison
|
boolean valid = false;
|
||||||
switch (comparator) {
|
// Perform the comparison
|
||||||
case "Greater":
|
switch (comparator) {
|
||||||
valid = actualPageCount > Integer.parseInt(pageCount);
|
case "Greater":
|
||||||
break;
|
valid = actualPageCount > Integer.parseInt(pageCount);
|
||||||
case "Equal":
|
break;
|
||||||
valid = actualPageCount == Integer.parseInt(pageCount);
|
case "Equal":
|
||||||
break;
|
valid = actualPageCount == Integer.parseInt(pageCount);
|
||||||
case "Less":
|
break;
|
||||||
valid = actualPageCount < Integer.parseInt(pageCount);
|
case "Less":
|
||||||
break;
|
valid = actualPageCount < Integer.parseInt(pageCount);
|
||||||
default:
|
break;
|
||||||
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
default:
|
||||||
}
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
|
}
|
||||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
|
||||||
return null;
|
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
|
|
||||||
@Operation(
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
|
||||||
summary = "Checks if a PDF is of a certain size",
|
@Operation(
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
summary = "Checks if a PDF is of a certain size",
|
||||||
public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request)
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
throws IOException, InterruptedException {
|
public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request)
|
||||||
MultipartFile inputFile = request.getFileInput();
|
throws IOException, InterruptedException {
|
||||||
String standardPageSize = request.getStandardPageSize();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String comparator = request.getComparator();
|
String standardPageSize = request.getStandardPageSize();
|
||||||
|
String comparator = request.getComparator();
|
||||||
// Load the PDF
|
|
||||||
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
// Load the PDF
|
||||||
|
PDDocument document = Loader.loadPDF(inputFile.getBytes());
|
||||||
PDPage firstPage = document.getPage(0);
|
|
||||||
PDRectangle actualPageSize = firstPage.getMediaBox();
|
PDPage firstPage = document.getPage(0);
|
||||||
|
PDRectangle actualPageSize = firstPage.getMediaBox();
|
||||||
// Calculate the area of the actual page size
|
|
||||||
float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
|
// Calculate the area of the actual page size
|
||||||
|
float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
|
||||||
// Get the standard size and calculate its area
|
|
||||||
PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
|
// Get the standard size and calculate its area
|
||||||
float standardArea = standardSize.getWidth() * standardSize.getHeight();
|
PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
|
||||||
|
float standardArea = standardSize.getWidth() * standardSize.getHeight();
|
||||||
boolean valid = false;
|
|
||||||
// Perform the comparison
|
boolean valid = false;
|
||||||
switch (comparator) {
|
// Perform the comparison
|
||||||
case "Greater":
|
switch (comparator) {
|
||||||
valid = actualArea > standardArea;
|
case "Greater":
|
||||||
break;
|
valid = actualArea > standardArea;
|
||||||
case "Equal":
|
break;
|
||||||
valid = actualArea == standardArea;
|
case "Equal":
|
||||||
break;
|
valid = actualArea == standardArea;
|
||||||
case "Less":
|
break;
|
||||||
valid = actualArea < standardArea;
|
case "Less":
|
||||||
break;
|
valid = actualArea < standardArea;
|
||||||
default:
|
break;
|
||||||
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
default:
|
||||||
}
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
|
}
|
||||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
|
||||||
return null;
|
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
|
|
||||||
@Operation(
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
|
||||||
summary = "Checks if a PDF is a set file size",
|
@Operation(
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
summary = "Checks if a PDF is a set file size",
|
||||||
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request)
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
throws IOException, InterruptedException {
|
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request)
|
||||||
MultipartFile inputFile = request.getFileInput();
|
throws IOException, InterruptedException {
|
||||||
String fileSize = request.getFileSize();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String comparator = request.getComparator();
|
String fileSize = request.getFileSize();
|
||||||
|
String comparator = request.getComparator();
|
||||||
// Get the file size
|
|
||||||
long actualFileSize = inputFile.getSize();
|
// Get the file size
|
||||||
|
long actualFileSize = inputFile.getSize();
|
||||||
boolean valid = false;
|
|
||||||
// Perform the comparison
|
boolean valid = false;
|
||||||
switch (comparator) {
|
// Perform the comparison
|
||||||
case "Greater":
|
switch (comparator) {
|
||||||
valid = actualFileSize > Long.parseLong(fileSize);
|
case "Greater":
|
||||||
break;
|
valid = actualFileSize > Long.parseLong(fileSize);
|
||||||
case "Equal":
|
break;
|
||||||
valid = actualFileSize == Long.parseLong(fileSize);
|
case "Equal":
|
||||||
break;
|
valid = actualFileSize == Long.parseLong(fileSize);
|
||||||
case "Less":
|
break;
|
||||||
valid = actualFileSize < Long.parseLong(fileSize);
|
case "Less":
|
||||||
break;
|
valid = actualFileSize < Long.parseLong(fileSize);
|
||||||
default:
|
break;
|
||||||
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
default:
|
||||||
}
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
|
}
|
||||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
|
||||||
return null;
|
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
|
|
||||||
@Operation(
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
|
||||||
summary = "Checks if a PDF is of a certain rotation",
|
@Operation(
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
summary = "Checks if a PDF is of a certain rotation",
|
||||||
public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request)
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
throws IOException, InterruptedException {
|
public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request)
|
||||||
MultipartFile inputFile = request.getFileInput();
|
throws IOException, InterruptedException {
|
||||||
int rotation = request.getRotation();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String comparator = request.getComparator();
|
int rotation = request.getRotation();
|
||||||
|
String comparator = request.getComparator();
|
||||||
// Load the PDF
|
|
||||||
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
// Load the PDF
|
||||||
|
PDDocument document = Loader.loadPDF(inputFile.getBytes());
|
||||||
// Get the rotation of the first page
|
|
||||||
PDPage firstPage = document.getPage(0);
|
// Get the rotation of the first page
|
||||||
int actualRotation = firstPage.getRotation();
|
PDPage firstPage = document.getPage(0);
|
||||||
boolean valid = false;
|
int actualRotation = firstPage.getRotation();
|
||||||
// Perform the comparison
|
boolean valid = false;
|
||||||
switch (comparator) {
|
// Perform the comparison
|
||||||
case "Greater":
|
switch (comparator) {
|
||||||
valid = actualRotation > rotation;
|
case "Greater":
|
||||||
break;
|
valid = actualRotation > rotation;
|
||||||
case "Equal":
|
break;
|
||||||
valid = actualRotation == rotation;
|
case "Equal":
|
||||||
break;
|
valid = actualRotation == rotation;
|
||||||
case "Less":
|
break;
|
||||||
valid = actualRotation < rotation;
|
case "Less":
|
||||||
break;
|
valid = actualRotation < rotation;
|
||||||
default:
|
break;
|
||||||
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
default:
|
||||||
}
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
|
}
|
||||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
|
||||||
return null;
|
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
}
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
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.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.apache.pdfbox.text.TextPosition;
|
import org.apache.pdfbox.text.TextPosition;
|
||||||
@@ -43,7 +44,7 @@ public class AutoRenameController {
|
|||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();
|
Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(file.getInputStream());
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
PDFTextStripper reader =
|
PDFTextStripper reader =
|
||||||
new PDFTextStripper() {
|
new PDFTextStripper() {
|
||||||
class LineInfo {
|
class LineInfo {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.awt.image.DataBufferByte;
|
|||||||
import java.awt.image.DataBufferInt;
|
import java.awt.image.DataBufferInt;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
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;
|
||||||
@@ -13,6 +12,7 @@ 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.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -42,7 +42,8 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class AutoSplitPdfController {
|
public class AutoSplitPdfController {
|
||||||
|
|
||||||
private static final String QR_CONTENT = "https://github.com/Frooodle/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";
|
||||||
|
|
||||||
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
@@ -54,8 +55,7 @@ public class AutoSplitPdfController {
|
|||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
boolean duplexMode = request.isDuplexMode();
|
boolean duplexMode = request.isDuplexMode();
|
||||||
|
|
||||||
InputStream inputStream = file.getInputStream();
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
PDDocument document = PDDocument.load(inputStream);
|
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
|
|
||||||
List<PDDocument> splitDocuments = new ArrayList<>();
|
List<PDDocument> splitDocuments = new ArrayList<>();
|
||||||
@@ -64,12 +64,11 @@ public class AutoSplitPdfController {
|
|||||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
|
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
|
||||||
String result = decodeQRCode(bim);
|
String result = decodeQRCode(bim);
|
||||||
|
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
|
||||||
if (QR_CONTENT.equals(result) && page != 0) {
|
|
||||||
splitDocuments.add(new PDDocument());
|
splitDocuments.add(new PDDocument());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!splitDocuments.isEmpty() && !QR_CONTENT.equals(result)) {
|
if (!splitDocuments.isEmpty() && !QR_CONTENT.equals(result) && !QR_CONTENT_OLD.equals(result)) {
|
||||||
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
|
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
|
||||||
} else if (page == 0) {
|
} else if (page == 0) {
|
||||||
PDDocument firstDocument = new PDDocument();
|
PDDocument firstDocument = new PDDocument();
|
||||||
@@ -78,7 +77,7 @@ public class AutoSplitPdfController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If duplexMode is true and current page is a divider, then skip next page
|
// If duplexMode is true and current page is a divider, then skip next page
|
||||||
if (duplexMode && QR_CONTENT.equals(result)) {
|
if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) {
|
||||||
page++;
|
page++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.util.stream.IntStream;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -32,7 +33,6 @@ 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.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -53,7 +53,7 @@ public class BlankPageController {
|
|||||||
|
|
||||||
PDDocument document = null;
|
PDDocument document = null;
|
||||||
try {
|
try {
|
||||||
document = PDDocument.load(inputFile.getInputStream());
|
document = Loader.loadPDF(inputFile.getBytes());
|
||||||
PDPageTree pages = document.getDocumentCatalog().getPages();
|
PDPageTree pages = document.getDocumentCatalog().getPages();
|
||||||
PDFTextStripper textStripper = new PDFTextStripper();
|
PDFTextStripper textStripper = new PDFTextStripper();
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ public class BlankPageController {
|
|||||||
List<String> command =
|
List<String> command =
|
||||||
new ArrayList<>(
|
new ArrayList<>(
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
"python3",
|
"python",
|
||||||
System.getProperty("user.dir")
|
System.getProperty("user.dir")
|
||||||
+ "/scripts/detect-blank-pages.py",
|
+ "/scripts/detect-blank-pages.py",
|
||||||
tempFile.toString(),
|
tempFile.toString(),
|
||||||
@@ -93,18 +93,25 @@ public class BlankPageController {
|
|||||||
"--white_percent",
|
"--white_percent",
|
||||||
String.valueOf(whitePercent)));
|
String.valueOf(whitePercent)));
|
||||||
|
|
||||||
|
Boolean blank = false;
|
||||||
// Run CLI command
|
// Run CLI command
|
||||||
ProcessExecutorResult returnCode =
|
try {
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// From detect-blank-pages.py
|
||||||
|
// Return code 1: The image is considered blank.
|
||||||
|
// Return code 0: The image is not considered blank.
|
||||||
|
// Since the process returned with a failure code, it should be blank.
|
||||||
|
blank = true;
|
||||||
|
}
|
||||||
|
|
||||||
// does contain data
|
if (blank) {
|
||||||
if (returnCode.getRc() == 0) {
|
System.out.println("Skipping, Image was blank for page #" + pageIndex);
|
||||||
|
} else {
|
||||||
System.out.println(
|
System.out.println(
|
||||||
"page " + pageIndex + " has image which is not blank");
|
"page " + pageIndex + " has image which is not blank");
|
||||||
pagesToKeepIndex.add(pageIndex);
|
pagesToKeepIndex.add(pageIndex);
|
||||||
} else {
|
|
||||||
System.out.println("Skipping, Image was blank for page #" + pageIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.util.List;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@@ -147,7 +148,7 @@ public class CompressController {
|
|||||||
if (expectedOutputSize != null && autoMode) {
|
if (expectedOutputSize != null && autoMode) {
|
||||||
long outputFileSize = Files.size(tempOutputFile);
|
long outputFileSize = Files.size(tempOutputFile);
|
||||||
if (outputFileSize > expectedOutputSize) {
|
if (outputFileSize > expectedOutputSize) {
|
||||||
try (PDDocument doc = PDDocument.load(new File(tempOutputFile.toString()))) {
|
try (PDDocument doc = Loader.loadPDF(new File(tempOutputFile.toString()))) {
|
||||||
long previousFileSize = 0;
|
long previousFileSize = 0;
|
||||||
double scaleFactor = 1.0;
|
double scaleFactor = 1.0;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -17,6 +16,7 @@ import java.util.zip.ZipOutputStream;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
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;
|
||||||
@@ -76,8 +76,7 @@ public class ExtractImageScansController {
|
|||||||
// Check if input file is a PDF
|
// Check if input file is a PDF
|
||||||
if (extension.equalsIgnoreCase("pdf")) {
|
if (extension.equalsIgnoreCase("pdf")) {
|
||||||
// Load PDF document
|
// Load PDF document
|
||||||
try (PDDocument document =
|
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) {
|
||||||
PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
|
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
int pageCount = document.getNumberOfPages();
|
int pageCount = document.getNumberOfPages();
|
||||||
images = new ArrayList<>();
|
images = new ArrayList<>();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import java.util.zip.ZipOutputStream;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@@ -53,7 +54,7 @@ public class ExtractImagesController {
|
|||||||
|
|
||||||
System.out.println(
|
System.out.println(
|
||||||
System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
|
System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
|
||||||
PDDocument document = PDDocument.load(file.getBytes());
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
|
|
||||||
// Create ByteArrayOutputStream to write zip file to byte array
|
// Create ByteArrayOutputStream to write zip file to byte array
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import java.util.Random;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -28,7 +29,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -49,7 +49,7 @@ public class FakeScanControllerWIP {
|
|||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@Hidden
|
@Hidden
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/fakeScan")
|
// @PostMapping(consumes = "multipart/form-data", value = "/fakeScan")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Repair a PDF file",
|
summary = "Repair a PDF file",
|
||||||
description =
|
description =
|
||||||
@@ -57,7 +57,7 @@ public class FakeScanControllerWIP {
|
|||||||
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) throws IOException {
|
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) throws IOException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(inputFile.getBytes());
|
PDDocument document = Loader.loadPDF(inputFile.getBytes());
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||||
BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
|
BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.Calendar;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||||
@@ -67,7 +68,7 @@ public class MetadataController {
|
|||||||
allRequestParams = new java.util.HashMap<String, String>();
|
allRequestParams = new java.util.HashMap<String, String>();
|
||||||
}
|
}
|
||||||
// Load the PDF file into a PDDocument
|
// Load the PDF file into a PDDocument
|
||||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||||
|
|
||||||
// Get the document information from the PDF
|
// Get the document information from the PDF
|
||||||
PDDocumentInformation info = document.getDocumentInformation();
|
PDDocumentInformation info = document.getDocumentInformation();
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class OverlayImageController {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Overlay image onto a PDF file",
|
summary = "Overlay image onto a PDF file",
|
||||||
description =
|
description =
|
||||||
"This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO")
|
"This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> overlayImage(@ModelAttribute OverlayImageRequest request) {
|
public ResponseEntity<byte[]> overlayImage(@ModelAttribute OverlayImageRequest request) {
|
||||||
MultipartFile pdfFile = request.getFileInput();
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
MultipartFile imageFile = request.getImageFile();
|
MultipartFile imageFile = request.getImageFile();
|
||||||
|
|||||||
@@ -1,150 +1,151 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.slf4j.Logger;
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||||
import org.springframework.http.MediaType;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
@RestController
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RequestMapping("/api/v1/misc")
|
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@RestController
|
||||||
public class PageNumbersController {
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
public class PageNumbersController {
|
||||||
|
|
||||||
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
||||||
@Operation(
|
|
||||||
summary = "Add page numbers to a PDF document",
|
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
||||||
description =
|
@Operation(
|
||||||
"This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
|
summary = "Add page numbers to a PDF document",
|
||||||
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
|
description =
|
||||||
throws IOException {
|
"This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
|
||||||
MultipartFile file = request.getFileInput();
|
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
|
||||||
String customMargin = request.getCustomMargin();
|
throws IOException {
|
||||||
int position = request.getPosition();
|
MultipartFile file = request.getFileInput();
|
||||||
int startingNumber = request.getStartingNumber();
|
String customMargin = request.getCustomMargin();
|
||||||
String pagesToNumber = request.getPagesToNumber();
|
int position = request.getPosition();
|
||||||
String customText = request.getCustomText();
|
int startingNumber = request.getStartingNumber();
|
||||||
int pageNumber = startingNumber;
|
String pagesToNumber = request.getPagesToNumber();
|
||||||
byte[] fileBytes = file.getBytes();
|
String customText = request.getCustomText();
|
||||||
PDDocument document = PDDocument.load(fileBytes);
|
int pageNumber = startingNumber;
|
||||||
|
byte[] fileBytes = file.getBytes();
|
||||||
float marginFactor;
|
PDDocument document = Loader.loadPDF(fileBytes);
|
||||||
switch (customMargin.toLowerCase()) {
|
|
||||||
case "small":
|
float marginFactor;
|
||||||
marginFactor = 0.02f;
|
switch (customMargin.toLowerCase()) {
|
||||||
break;
|
case "small":
|
||||||
case "medium":
|
marginFactor = 0.02f;
|
||||||
marginFactor = 0.035f;
|
break;
|
||||||
break;
|
case "medium":
|
||||||
case "large":
|
marginFactor = 0.035f;
|
||||||
marginFactor = 0.05f;
|
break;
|
||||||
break;
|
case "large":
|
||||||
case "x-large":
|
marginFactor = 0.05f;
|
||||||
marginFactor = 0.075f;
|
break;
|
||||||
break;
|
case "x-large":
|
||||||
|
marginFactor = 0.075f;
|
||||||
default:
|
break;
|
||||||
marginFactor = 0.035f;
|
|
||||||
break;
|
default:
|
||||||
}
|
marginFactor = 0.035f;
|
||||||
|
break;
|
||||||
float fontSize = 12.0f;
|
}
|
||||||
PDType1Font font = PDType1Font.HELVETICA;
|
|
||||||
if (pagesToNumber == null || pagesToNumber.length() == 0) {
|
float fontSize = 12.0f;
|
||||||
pagesToNumber = "all";
|
if (pagesToNumber == null || pagesToNumber.length() == 0) {
|
||||||
}
|
pagesToNumber = "all";
|
||||||
if (customText == null || customText.length() == 0) {
|
}
|
||||||
customText = "{n}";
|
if (customText == null || customText.length() == 0) {
|
||||||
}
|
customText = "{n}";
|
||||||
List<Integer> pagesToNumberList =
|
}
|
||||||
GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages());
|
List<Integer> pagesToNumberList =
|
||||||
|
GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages());
|
||||||
for (int i : pagesToNumberList) {
|
|
||||||
PDPage page = document.getPage(i);
|
for (int i : pagesToNumberList) {
|
||||||
PDRectangle pageSize = page.getMediaBox();
|
PDPage page = document.getPage(i);
|
||||||
|
PDRectangle pageSize = page.getMediaBox();
|
||||||
String text =
|
|
||||||
customText != null
|
String text =
|
||||||
? customText
|
customText != null
|
||||||
.replace("{n}", String.valueOf(pageNumber))
|
? customText
|
||||||
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
.replace("{n}", String.valueOf(pageNumber))
|
||||||
.replace(
|
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
||||||
"{filename}",
|
.replace(
|
||||||
file.getOriginalFilename()
|
"{filename}",
|
||||||
.replaceFirst("[.][^.]+$", ""))
|
file.getOriginalFilename()
|
||||||
: String.valueOf(pageNumber);
|
.replaceFirst("[.][^.]+$", ""))
|
||||||
|
: String.valueOf(pageNumber);
|
||||||
float x, y;
|
|
||||||
|
float x, y;
|
||||||
int xGroup = (position - 1) % 3;
|
|
||||||
int yGroup = 2 - (position - 1) / 3;
|
int xGroup = (position - 1) % 3;
|
||||||
|
int yGroup = 2 - (position - 1) / 3;
|
||||||
switch (xGroup) {
|
|
||||||
case 0: // left
|
switch (xGroup) {
|
||||||
x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
|
case 0: // left
|
||||||
break;
|
x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
|
||||||
case 1: // center
|
break;
|
||||||
x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
|
case 1: // center
|
||||||
break;
|
x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
|
||||||
default: // right
|
break;
|
||||||
x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();
|
default: // right
|
||||||
break;
|
x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
switch (yGroup) {
|
|
||||||
case 0: // bottom
|
switch (yGroup) {
|
||||||
y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
|
case 0: // bottom
|
||||||
break;
|
y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
|
||||||
case 1: // middle
|
break;
|
||||||
y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
|
case 1: // middle
|
||||||
break;
|
y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
|
||||||
default: // top
|
break;
|
||||||
y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();
|
default: // top
|
||||||
break;
|
y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
PDPageContentStream contentStream =
|
|
||||||
new PDPageContentStream(
|
PDPageContentStream contentStream =
|
||||||
document, page, PDPageContentStream.AppendMode.APPEND, true);
|
new PDPageContentStream(
|
||||||
contentStream.beginText();
|
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
contentStream.setFont(font, fontSize);
|
contentStream.beginText();
|
||||||
contentStream.newLineAtOffset(x, y);
|
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), fontSize);
|
||||||
contentStream.showText(text);
|
contentStream.newLineAtOffset(x, y);
|
||||||
contentStream.endText();
|
contentStream.showText(text);
|
||||||
contentStream.close();
|
contentStream.endText();
|
||||||
|
contentStream.close();
|
||||||
pageNumber++;
|
|
||||||
}
|
pageNumber++;
|
||||||
|
}
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
document.save(baos);
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
document.close();
|
document.save(baos);
|
||||||
|
document.close();
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
baos.toByteArray(),
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf",
|
baos.toByteArray(),
|
||||||
MediaType.APPLICATION_PDF);
|
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf",
|
||||||
}
|
MediaType.APPLICATION_PDF);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package stirling.software.SPDF.controller.api.misc;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||||
@@ -36,7 +37,7 @@ public class ShowJavascript {
|
|||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String script = "";
|
String script = "";
|
||||||
|
|
||||||
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
|
||||||
|
|
||||||
if (document.getDocumentCatalog() != null
|
if (document.getDocumentCatalog() != null
|
||||||
&& document.getDocumentCatalog().getNames() != null) {
|
&& document.getDocumentCatalog().getNames() != null) {
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||||
|
import org.apache.pdfbox.util.Matrix;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.misc.AddStampRequest;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
|
public class StampController {
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
|
||||||
|
@Operation(
|
||||||
|
summary = "Add stamp to a PDF file",
|
||||||
|
description =
|
||||||
|
"This endpoint adds a stamp to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> addStamp(@ModelAttribute AddStampRequest request)
|
||||||
|
throws IOException, Exception {
|
||||||
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
|
String watermarkType = request.getStampType();
|
||||||
|
String watermarkText = request.getStampText();
|
||||||
|
MultipartFile watermarkImage = request.getStampImage();
|
||||||
|
String alphabet = request.getAlphabet();
|
||||||
|
float fontSize = request.getFontSize();
|
||||||
|
float rotation = request.getRotation();
|
||||||
|
float opacity = request.getOpacity();
|
||||||
|
int position = request.getPosition(); // Updated to use 1-9 positioning logic
|
||||||
|
float overrideX = request.getOverrideX(); // New field for X override
|
||||||
|
float overrideY = request.getOverrideY(); // New field for Y override
|
||||||
|
|
||||||
|
String customColor = request.getCustomColor();
|
||||||
|
float marginFactor;
|
||||||
|
switch (request.getCustomMargin().toLowerCase()) {
|
||||||
|
case "small":
|
||||||
|
marginFactor = 0.02f;
|
||||||
|
break;
|
||||||
|
case "medium":
|
||||||
|
marginFactor = 0.035f;
|
||||||
|
break;
|
||||||
|
case "large":
|
||||||
|
marginFactor = 0.05f;
|
||||||
|
break;
|
||||||
|
case "x-large":
|
||||||
|
marginFactor = 0.075f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
marginFactor = 0.035f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the input PDF
|
||||||
|
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||||
|
|
||||||
|
for (PDPage page : document.getPages()) {
|
||||||
|
PDPageContentStream contentStream =
|
||||||
|
new PDPageContentStream(
|
||||||
|
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
|
|
||||||
|
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
||||||
|
graphicsState.setNonStrokingAlphaConstant(opacity);
|
||||||
|
contentStream.setGraphicsStateParameters(graphicsState);
|
||||||
|
|
||||||
|
if (watermarkType.equalsIgnoreCase("text")) {
|
||||||
|
addTextStamp(
|
||||||
|
contentStream,
|
||||||
|
watermarkText,
|
||||||
|
document,
|
||||||
|
page,
|
||||||
|
rotation,
|
||||||
|
position,
|
||||||
|
fontSize,
|
||||||
|
alphabet,
|
||||||
|
overrideX,
|
||||||
|
overrideY,
|
||||||
|
marginFactor,
|
||||||
|
customColor);
|
||||||
|
} else if (watermarkType.equalsIgnoreCase("image")) {
|
||||||
|
addImageStamp(
|
||||||
|
contentStream,
|
||||||
|
watermarkImage,
|
||||||
|
document,
|
||||||
|
page,
|
||||||
|
rotation,
|
||||||
|
position,
|
||||||
|
fontSize,
|
||||||
|
overrideX,
|
||||||
|
overrideY,
|
||||||
|
marginFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
|
document,
|
||||||
|
pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTextStamp(
|
||||||
|
PDPageContentStream contentStream,
|
||||||
|
String watermarkText,
|
||||||
|
PDDocument document,
|
||||||
|
PDPage page,
|
||||||
|
float rotation,
|
||||||
|
int position, // 1-9 positioning logic
|
||||||
|
float fontSize,
|
||||||
|
String alphabet,
|
||||||
|
float overrideX, // X override
|
||||||
|
float overrideY,
|
||||||
|
float marginFactor,
|
||||||
|
String colorString) // Y override
|
||||||
|
throws IOException {
|
||||||
|
String resourceDir = "";
|
||||||
|
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||||
|
switch (alphabet) {
|
||||||
|
case "arabic":
|
||||||
|
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
|
||||||
|
break;
|
||||||
|
case "japanese":
|
||||||
|
resourceDir = "static/fonts/Meiryo.ttf";
|
||||||
|
break;
|
||||||
|
case "korean":
|
||||||
|
resourceDir = "static/fonts/malgun.ttf";
|
||||||
|
break;
|
||||||
|
case "chinese":
|
||||||
|
resourceDir = "static/fonts/SimSun.ttf";
|
||||||
|
break;
|
||||||
|
case "roman":
|
||||||
|
default:
|
||||||
|
resourceDir = "static/fonts/NotoSans-Regular.ttf";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resourceDir.equals("")) {
|
||||||
|
ClassPathResource classPathResource = new ClassPathResource(resourceDir);
|
||||||
|
String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
|
||||||
|
File tempFile = File.createTempFile("NotoSansFont", fileExtension);
|
||||||
|
try (InputStream is = classPathResource.getInputStream();
|
||||||
|
FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||||
|
IOUtils.copy(is, os);
|
||||||
|
}
|
||||||
|
|
||||||
|
font = PDType0Font.load(document, tempFile);
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
contentStream.setFont(font, fontSize);
|
||||||
|
|
||||||
|
Color redactColor;
|
||||||
|
try {
|
||||||
|
if (!colorString.startsWith("#")) {
|
||||||
|
colorString = "#" + colorString;
|
||||||
|
}
|
||||||
|
redactColor = Color.decode(colorString);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
|
||||||
|
redactColor = Color.LIGHT_GRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
contentStream.setNonStrokingColor(redactColor);
|
||||||
|
|
||||||
|
PDRectangle pageSize = page.getMediaBox();
|
||||||
|
float x, y;
|
||||||
|
|
||||||
|
if (overrideX >= 0 && overrideY >= 0) {
|
||||||
|
// Use override values if provided
|
||||||
|
x = overrideX;
|
||||||
|
y = overrideY;
|
||||||
|
} else {
|
||||||
|
x =
|
||||||
|
calculatePositionX(
|
||||||
|
pageSize,
|
||||||
|
position,
|
||||||
|
fontSize,
|
||||||
|
font,
|
||||||
|
fontSize,
|
||||||
|
watermarkText,
|
||||||
|
marginFactor);
|
||||||
|
y = calculatePositionY(pageSize, position, fontSize, marginFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentStream.beginText();
|
||||||
|
contentStream.setTextMatrix(Matrix.getRotateInstance(Math.toRadians(rotation), x, y));
|
||||||
|
contentStream.showText(watermarkText);
|
||||||
|
contentStream.endText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addImageStamp(
|
||||||
|
PDPageContentStream contentStream,
|
||||||
|
MultipartFile watermarkImage,
|
||||||
|
PDDocument document,
|
||||||
|
PDPage page,
|
||||||
|
float rotation,
|
||||||
|
int position, // 1-9 positioning logic
|
||||||
|
float fontSize,
|
||||||
|
float overrideX,
|
||||||
|
float overrideY,
|
||||||
|
float marginFactor)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
// Load the watermark image
|
||||||
|
BufferedImage image = ImageIO.read(watermarkImage.getInputStream());
|
||||||
|
|
||||||
|
// Compute width based on original aspect ratio
|
||||||
|
float aspectRatio = (float) image.getWidth() / (float) image.getHeight();
|
||||||
|
|
||||||
|
// Desired physical height (in PDF points)
|
||||||
|
float desiredPhysicalHeight = fontSize;
|
||||||
|
|
||||||
|
// Desired physical width based on the aspect ratio
|
||||||
|
float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio;
|
||||||
|
|
||||||
|
// Convert the BufferedImage to PDImageXObject
|
||||||
|
PDImageXObject xobject = LosslessFactory.createFromImage(document, image);
|
||||||
|
|
||||||
|
PDRectangle pageSize = page.getMediaBox();
|
||||||
|
float x, y;
|
||||||
|
|
||||||
|
if (overrideX >= 0 && overrideY >= 0) {
|
||||||
|
// Use override values if provided
|
||||||
|
x = overrideX;
|
||||||
|
y = overrideY;
|
||||||
|
} else {
|
||||||
|
x =
|
||||||
|
calculatePositionX(
|
||||||
|
pageSize, position, desiredPhysicalWidth, null, 0, null, marginFactor);
|
||||||
|
y = calculatePositionY(pageSize, position, fontSize, marginFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentStream.saveGraphicsState();
|
||||||
|
contentStream.transform(Matrix.getTranslateInstance(x, y));
|
||||||
|
contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));
|
||||||
|
contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight);
|
||||||
|
contentStream.restoreGraphicsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private float calculatePositionX(
|
||||||
|
PDRectangle pageSize,
|
||||||
|
int position,
|
||||||
|
float contentWidth,
|
||||||
|
PDFont font,
|
||||||
|
float fontSize,
|
||||||
|
String text,
|
||||||
|
float marginFactor)
|
||||||
|
throws IOException {
|
||||||
|
float actualWidth =
|
||||||
|
(text != null) ? calculateTextWidth(text, font, fontSize) : contentWidth;
|
||||||
|
switch (position % 3) {
|
||||||
|
case 1: // Left
|
||||||
|
return pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
|
||||||
|
case 2: // Center
|
||||||
|
return (pageSize.getWidth() - actualWidth) / 2;
|
||||||
|
case 0: // Right
|
||||||
|
return pageSize.getUpperRightX() - actualWidth - marginFactor * pageSize.getWidth();
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float calculateTextWidth(String text, PDFont font, float fontSize) throws IOException {
|
||||||
|
return font.getStringWidth(text) / 1000 * fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float calculatePositionY(
|
||||||
|
PDRectangle pageSize, int position, float height, float marginFactor) {
|
||||||
|
switch ((position - 1) / 3) {
|
||||||
|
case 0: // Top
|
||||||
|
return pageSize.getUpperRightY() - height - marginFactor * pageSize.getHeight();
|
||||||
|
case 1: // Middle
|
||||||
|
return (pageSize.getHeight() - height) / 2;
|
||||||
|
case 2: // Bottom
|
||||||
|
return pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package stirling.software.SPDF.controller.api.pipeline;
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -39,6 +41,60 @@ public class ApiDocService {
|
|||||||
return "http://localhost:" + port + contextPath + "/v1/api-docs";
|
return "http://localhost:" + port + contextPath + "/v1/api-docs";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, List<String>> outputToFileTypes = new HashMap<>();
|
||||||
|
|
||||||
|
public List getExtensionTypes(boolean output, String operationName) {
|
||||||
|
if (outputToFileTypes.size() == 0) {
|
||||||
|
outputToFileTypes.put("PDF", Arrays.asList("pdf"));
|
||||||
|
outputToFileTypes.put(
|
||||||
|
"IMAGE",
|
||||||
|
Arrays.asList(
|
||||||
|
"png", "jpg", "jpeg", "gif", "webp", "bmp", "tif", "tiff", "svg", "psd",
|
||||||
|
"ai", "eps"));
|
||||||
|
outputToFileTypes.put(
|
||||||
|
"ZIP",
|
||||||
|
Arrays.asList("zip", "rar", "7z", "tar", "gz", "bz2", "xz", "lz", "lzma", "z"));
|
||||||
|
outputToFileTypes.put("WORD", Arrays.asList("doc", "docx", "odt", "rtf"));
|
||||||
|
outputToFileTypes.put("CSV", Arrays.asList("csv"));
|
||||||
|
outputToFileTypes.put("JS", Arrays.asList("js", "jsx"));
|
||||||
|
outputToFileTypes.put("HTML", Arrays.asList("html", "htm", "xhtml"));
|
||||||
|
outputToFileTypes.put("JSON", Arrays.asList("json"));
|
||||||
|
outputToFileTypes.put("TXT", Arrays.asList("txt", "text", "md", "markdown"));
|
||||||
|
outputToFileTypes.put("PPT", Arrays.asList("ppt", "pptx", "odp"));
|
||||||
|
outputToFileTypes.put("XML", Arrays.asList("xml", "xsd", "xsl"));
|
||||||
|
outputToFileTypes.put(
|
||||||
|
"BOOK",
|
||||||
|
Arrays.asList(
|
||||||
|
"epub", "mobi", "azw3", "fb2", "txt",
|
||||||
|
"docx"));
|
||||||
|
// type.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiDocsJsonRootNode == null || apiDocumentation.size() == 0) {
|
||||||
|
loadApiDocumentation();
|
||||||
|
}
|
||||||
|
if (!apiDocumentation.containsKey(operationName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiEndpoint endpoint = apiDocumentation.get(operationName);
|
||||||
|
String description = endpoint.getDescription();
|
||||||
|
Pattern pattern = null;
|
||||||
|
if (output) {
|
||||||
|
pattern = Pattern.compile("Output:(\\w+)");
|
||||||
|
} else {
|
||||||
|
pattern = Pattern.compile("Input:(\\w+)");
|
||||||
|
}
|
||||||
|
Matcher matcher = pattern.matcher(description);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String type = matcher.group(1).toUpperCase();
|
||||||
|
if (outputToFileTypes.containsKey(type)) {
|
||||||
|
return outputToFileTypes.get(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private UserServiceInterface userService;
|
private UserServiceInterface userService;
|
||||||
|
|
||||||
|
|||||||
@@ -1,114 +1,133 @@
|
|||||||
package stirling.software.SPDF.controller.api.pipeline;
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.HashMap;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.List;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
import org.slf4j.Logger;
|
import java.util.zip.ZipOutputStream;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.core.io.Resource;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.model.PipelineConfig;
|
|
||||||
import stirling.software.SPDF.model.api.HandleDataRequest;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
|
import stirling.software.SPDF.model.api.HandleDataRequest;
|
||||||
@RestController
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RequestMapping("/api/v1/pipeline")
|
|
||||||
@Tag(name = "Pipeline", description = "Pipeline APIs")
|
@RestController
|
||||||
public class PipelineController {
|
@RequestMapping("/api/v1/pipeline")
|
||||||
|
@Tag(name = "Pipeline", description = "Pipeline APIs")
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class);
|
public class PipelineController {
|
||||||
|
|
||||||
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class);
|
||||||
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
|
||||||
@Autowired PipelineProcessor processor;
|
final String watchedFoldersDir = "./pipeline/watchedFolders/";
|
||||||
|
final String finishedFoldersDir = "./pipeline/finishedFolders/";
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired PipelineProcessor processor;
|
||||||
|
|
||||||
@Autowired private ObjectMapper objectMapper;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@PostMapping("/handleData")
|
@Autowired private ObjectMapper objectMapper;
|
||||||
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
|
|
||||||
throws JsonMappingException, JsonProcessingException {
|
@PostMapping("/handleData")
|
||||||
if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) {
|
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
|
||||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
throws JsonMappingException, JsonProcessingException {
|
||||||
}
|
if (!Boolean.TRUE.equals(applicationProperties.getSystem().getEnableAlphaFunctionality())) {
|
||||||
|
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||||
MultipartFile[] files = request.getFileInput();
|
}
|
||||||
String jsonString = request.getJson();
|
|
||||||
if (files == null) {
|
MultipartFile[] files = request.getFileInput();
|
||||||
return null;
|
String jsonString = request.getJson();
|
||||||
}
|
if (files == null) {
|
||||||
PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class);
|
return null;
|
||||||
logger.info("Received POST request to /handleData with {} files", files.length);
|
}
|
||||||
try {
|
PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class);
|
||||||
List<Resource> inputFiles = processor.generateInputFiles(files);
|
logger.info("Received POST request to /handleData with {} files", files.length);
|
||||||
if (inputFiles == null || inputFiles.size() == 0) {
|
try {
|
||||||
return null;
|
List<Resource> inputFiles = processor.generateInputFiles(files);
|
||||||
}
|
if (inputFiles == null || inputFiles.size() == 0) {
|
||||||
List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config);
|
return null;
|
||||||
if (outputFiles != null && outputFiles.size() == 1) {
|
}
|
||||||
// If there is only one file, return it directly
|
List<Resource> outputFiles = processor.runPipelineAgainstFiles(inputFiles, config);
|
||||||
Resource singleFile = outputFiles.get(0);
|
if (outputFiles != null && outputFiles.size() == 1) {
|
||||||
InputStream is = singleFile.getInputStream();
|
// If there is only one file, return it directly
|
||||||
byte[] bytes = new byte[(int) singleFile.contentLength()];
|
Resource singleFile = outputFiles.get(0);
|
||||||
is.read(bytes);
|
InputStream is = singleFile.getInputStream();
|
||||||
is.close();
|
byte[] bytes = new byte[(int) singleFile.contentLength()];
|
||||||
|
is.read(bytes);
|
||||||
logger.info("Returning single file response...");
|
is.close();
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM);
|
logger.info("Returning single file response...");
|
||||||
} else if (outputFiles == null) {
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
return null;
|
bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM);
|
||||||
}
|
} else if (outputFiles == null) {
|
||||||
|
return null;
|
||||||
// Create a ByteArrayOutputStream to hold the zip
|
}
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
ZipOutputStream zipOut = new ZipOutputStream(baos);
|
// Create a ByteArrayOutputStream to hold the zip
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
// Loop through each file and add it to the zip
|
ZipOutputStream zipOut = new ZipOutputStream(baos);
|
||||||
for (Resource file : outputFiles) {
|
|
||||||
ZipEntry zipEntry = new ZipEntry(file.getFilename());
|
// A map to keep track of filenames and their counts
|
||||||
zipOut.putNextEntry(zipEntry);
|
Map<String, Integer> filenameCount = new HashMap<>();
|
||||||
|
|
||||||
// Read the file into a byte array
|
// Loop through each file and add it to the zip
|
||||||
InputStream is = file.getInputStream();
|
for (Resource file : outputFiles) {
|
||||||
byte[] bytes = new byte[(int) file.contentLength()];
|
String originalFilename = file.getFilename();
|
||||||
is.read(bytes);
|
String filename = originalFilename;
|
||||||
|
|
||||||
// Write the bytes of the file to the zip
|
// Check if the filename already exists, and modify it if necessary
|
||||||
zipOut.write(bytes, 0, bytes.length);
|
if (filenameCount.containsKey(originalFilename)) {
|
||||||
zipOut.closeEntry();
|
int count = filenameCount.get(originalFilename);
|
||||||
|
String baseName = originalFilename.replaceAll("\\.[^.]*$", "");
|
||||||
is.close();
|
String extension = originalFilename.replaceAll("^.*\\.", "");
|
||||||
}
|
filename = baseName + "(" + count + ")." + extension;
|
||||||
|
filenameCount.put(originalFilename, count + 1);
|
||||||
zipOut.close();
|
} else {
|
||||||
|
filenameCount.put(originalFilename, 1);
|
||||||
logger.info("Returning zipped file response...");
|
}
|
||||||
return WebResponseUtils.boasToWebResponse(
|
|
||||||
baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM);
|
ZipEntry zipEntry = new ZipEntry(filename);
|
||||||
} catch (Exception e) {
|
zipOut.putNextEntry(zipEntry);
|
||||||
logger.error("Error handling data: ", e);
|
|
||||||
return null;
|
// Read the file into a byte array
|
||||||
}
|
InputStream is = file.getInputStream();
|
||||||
}
|
byte[] bytes = new byte[(int) file.contentLength()];
|
||||||
}
|
is.read(bytes);
|
||||||
|
|
||||||
|
// Write the bytes of the file to the zip
|
||||||
|
zipOut.write(bytes, 0, bytes.length);
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
zipOut.close();
|
||||||
|
|
||||||
|
logger.info("Returning zipped file response...");
|
||||||
|
return WebResponseUtils.boasToWebResponse(
|
||||||
|
baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error handling data: ", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -80,15 +83,11 @@ public class PipelineProcessor {
|
|||||||
operation,
|
operation,
|
||||||
isMultiInputOperation);
|
isMultiInputOperation);
|
||||||
Map<String, Object> parameters = pipelineOperation.getParameters();
|
Map<String, Object> parameters = pipelineOperation.getParameters();
|
||||||
String inputFileExtension = "";
|
List<String> inputFileTypes = apiDocService.getExtensionTypes(false, operation);
|
||||||
|
if (inputFileTypes == null) {
|
||||||
// TODO
|
inputFileTypes = new ArrayList<String>(Arrays.asList("ALL"));
|
||||||
// if (operationNode.has("inputFileType")) {
|
}
|
||||||
// inputFileExtension = operationNode.get("inputFileType").asText();
|
// List outputFileTypes = apiDocService.getExtensionTypes(true, operation);
|
||||||
// } else {
|
|
||||||
inputFileExtension = ".pdf";
|
|
||||||
// }
|
|
||||||
final String finalInputFileExtension = inputFileExtension;
|
|
||||||
|
|
||||||
String url = getBaseUrl() + operation;
|
String url = getBaseUrl() + operation;
|
||||||
|
|
||||||
@@ -96,55 +95,63 @@ public class PipelineProcessor {
|
|||||||
if (!isMultiInputOperation) {
|
if (!isMultiInputOperation) {
|
||||||
for (Resource file : outputFiles) {
|
for (Resource file : outputFiles) {
|
||||||
boolean hasInputFileType = false;
|
boolean hasInputFileType = false;
|
||||||
if (file.getFilename().endsWith(inputFileExtension)) {
|
for (String extension : inputFileTypes) {
|
||||||
hasInputFileType = true;
|
if (extension.equals("ALL") || file.getFilename().endsWith(extension)) {
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
hasInputFileType = true;
|
||||||
body.add("fileInput", file);
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||||
|
body.add("fileInput", file);
|
||||||
|
|
||||||
for (Entry<String, Object> entry : parameters.entrySet()) {
|
for (Entry<String, Object> entry : parameters.entrySet()) {
|
||||||
body.add(entry.getKey(), entry.getValue());
|
body.add(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
||||||
|
|
||||||
|
// If the operation is filter and the response body is null or empty,
|
||||||
|
// skip
|
||||||
|
// this
|
||||||
|
// file
|
||||||
|
if (operation.startsWith("filter-")
|
||||||
|
&& (response.getBody() == null
|
||||||
|
|| response.getBody().length == 0)) {
|
||||||
|
logger.info("Skipping file due to failing {}", operation);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
||||||
|
logPrintStream.println("Error: " + response.getBody());
|
||||||
|
hasErrors = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
processOutputFiles(operation, response, newOutputFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = sendWebRequest(url, body);
|
|
||||||
|
|
||||||
// If the operation is filter and the response body is null or empty, skip
|
|
||||||
// this
|
|
||||||
// file
|
|
||||||
if (operation.startsWith("filter-")
|
|
||||||
&& (response.getBody() == null || response.getBody().length == 0)) {
|
|
||||||
logger.info("Skipping file due to failing {}", operation);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.getStatusCode().equals(HttpStatus.OK)) {
|
|
||||||
logPrintStream.println("Error: " + response.getBody());
|
|
||||||
hasErrors = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
processOutputFiles(operation, file.getFilename(), response, newOutputFiles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasInputFileType) {
|
if (!hasInputFileType) {
|
||||||
logPrintStream.println(
|
logPrintStream.println(
|
||||||
"No files with extension "
|
"No files with extension "
|
||||||
+ inputFileExtension
|
+ String.join(", ", inputFileTypes)
|
||||||
+ " found for operation "
|
+ " found for operation "
|
||||||
+ operation);
|
+ operation);
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
outputFiles = newOutputFiles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Filter and collect all files that match the inputFileExtension
|
// Filter and collect all files that match the inputFileExtension
|
||||||
List<Resource> matchingFiles =
|
List<Resource> matchingFiles;
|
||||||
outputFiles.stream()
|
if (inputFileTypes.contains("ALL")) {
|
||||||
.filter(
|
matchingFiles = new ArrayList<>(outputFiles);
|
||||||
file ->
|
} else {
|
||||||
file.getFilename()
|
final List<String> finalinputFileTypes = inputFileTypes;
|
||||||
.endsWith(finalInputFileExtension))
|
matchingFiles =
|
||||||
.collect(Collectors.toList());
|
outputFiles.stream()
|
||||||
|
.filter(
|
||||||
|
file ->
|
||||||
|
finalinputFileTypes.stream()
|
||||||
|
.anyMatch(file.getFilename()::endsWith))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there are matching files
|
// Check if there are matching files
|
||||||
if (!matchingFiles.isEmpty()) {
|
if (!matchingFiles.isEmpty()) {
|
||||||
@@ -164,11 +171,7 @@ public class PipelineProcessor {
|
|||||||
|
|
||||||
// Handle the response
|
// Handle the response
|
||||||
if (response.getStatusCode().equals(HttpStatus.OK)) {
|
if (response.getStatusCode().equals(HttpStatus.OK)) {
|
||||||
processOutputFiles(
|
processOutputFiles(operation, response, newOutputFiles);
|
||||||
operation,
|
|
||||||
matchingFiles.get(0).getFilename(),
|
|
||||||
response,
|
|
||||||
newOutputFiles);
|
|
||||||
} else {
|
} else {
|
||||||
// Log error if the response status is not OK
|
// Log error if the response status is not OK
|
||||||
logPrintStream.println(
|
logPrintStream.println(
|
||||||
@@ -178,17 +181,19 @@ public class PipelineProcessor {
|
|||||||
} else {
|
} else {
|
||||||
logPrintStream.println(
|
logPrintStream.println(
|
||||||
"No files with extension "
|
"No files with extension "
|
||||||
+ inputFileExtension
|
+ String.join(", ", inputFileTypes)
|
||||||
+ " found for multi-input operation "
|
+ " found for multi-input operation "
|
||||||
+ operation);
|
+ operation);
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logPrintStream.close();
|
logPrintStream.close();
|
||||||
|
outputFiles = newOutputFiles;
|
||||||
}
|
}
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
logger.error("Errors occurred during processing. Log: {}", logStream.toString());
|
logger.error("Errors occurred during processing. Log: {}", logStream.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputFiles;
|
return outputFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +201,7 @@ public class PipelineProcessor {
|
|||||||
RestTemplate restTemplate = new RestTemplate();
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
// Set up headers, including API key
|
// Set up headers, including API key
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
String apiKey = getApiKeyForUser();
|
String apiKey = getApiKeyForUser();
|
||||||
headers.add("X-API-Key", apiKey);
|
headers.add("X-API-Key", apiKey);
|
||||||
@@ -208,22 +214,41 @@ public class PipelineProcessor {
|
|||||||
return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
|
return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String removeTrailingNaming(String filename) {
|
||||||
|
// Splitting filename into name and extension
|
||||||
|
int dotIndex = filename.lastIndexOf(".");
|
||||||
|
if (dotIndex == -1) {
|
||||||
|
// No extension found
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
String name = filename.substring(0, dotIndex);
|
||||||
|
String extension = filename.substring(dotIndex);
|
||||||
|
|
||||||
|
// Finding the last underscore
|
||||||
|
int underscoreIndex = name.lastIndexOf("_");
|
||||||
|
if (underscoreIndex == -1) {
|
||||||
|
// No underscore found
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removing the last part and reattaching the extension
|
||||||
|
return name.substring(0, underscoreIndex) + extension;
|
||||||
|
}
|
||||||
|
|
||||||
private List<Resource> processOutputFiles(
|
private List<Resource> processOutputFiles(
|
||||||
String operation,
|
String operation, ResponseEntity<byte[]> response, List<Resource> newOutputFiles)
|
||||||
String fileName,
|
|
||||||
ResponseEntity<byte[]> response,
|
|
||||||
List<Resource> newOutputFiles)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
// Define filename
|
// Define filename
|
||||||
String newFilename;
|
String newFilename;
|
||||||
if ("auto-rename".equals(operation)) {
|
if (operation.contains("auto-rename")) {
|
||||||
// If the operation is "auto-rename", generate a new filename.
|
// If the operation is "auto-rename", generate a new filename.
|
||||||
// This is a simple example of generating a filename using current timestamp.
|
// This is a simple example of generating a filename using current timestamp.
|
||||||
// Modify as per your needs.
|
// Modify as per your needs.
|
||||||
newFilename = "file_" + System.currentTimeMillis();
|
|
||||||
|
newFilename = extractFilename(response);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, keep the original filename.
|
// Otherwise, keep the original filename.
|
||||||
newFilename = fileName;
|
newFilename = removeTrailingNaming(extractFilename(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the response body is a zip file
|
// Check if the response body is a zip file
|
||||||
@@ -244,6 +269,28 @@ public class PipelineProcessor {
|
|||||||
return newOutputFiles;
|
return newOutputFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String extractFilename(ResponseEntity<byte[]> response) {
|
||||||
|
String filename = "default-filename.ext"; // Default filename if not found
|
||||||
|
|
||||||
|
HttpHeaders headers = response.getHeaders();
|
||||||
|
String contentDisposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION);
|
||||||
|
|
||||||
|
if (contentDisposition != null && !contentDisposition.isEmpty()) {
|
||||||
|
String[] parts = contentDisposition.split(";");
|
||||||
|
for (String part : parts) {
|
||||||
|
if (part.trim().startsWith("filename")) {
|
||||||
|
// Extracts filename and removes quotes if present
|
||||||
|
filename = part.split("=")[1].trim().replace("\"", "");
|
||||||
|
filename = URLDecoder.decode(filename, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
List<Resource> generateInputFiles(File[] files) throws Exception {
|
List<Resource> generateInputFiles(File[] files) throws Exception {
|
||||||
if (files == null || files.length == 0) {
|
if (files == null || files.length == 0) {
|
||||||
logger.info("No files");
|
logger.info("No files");
|
||||||
|
|||||||
@@ -4,44 +4,35 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.security.KeyFactory;
|
import java.io.OutputStream;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.examples.signature.CreateSignatureBase;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDResources;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
|
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
|
||||||
import org.bouncycastle.cms.CMSProcessableByteArray;
|
|
||||||
import org.bouncycastle.cms.CMSSignedData;
|
|
||||||
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
|
||||||
import org.bouncycastle.cms.CMSTypedData;
|
|
||||||
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.bouncycastle.operator.ContentSigner;
|
import org.bouncycastle.openssl.PEMDecryptorProvider;
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
|
||||||
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
import org.bouncycastle.openssl.PEMKeyPair;
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
import org.bouncycastle.openssl.PEMParser;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
|
||||||
|
import org.bouncycastle.operator.InputDecryptorProvider;
|
||||||
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
|
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
||||||
|
import org.bouncycastle.pkcs.PKCSException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -68,11 +59,22 @@ public class CertSignController {
|
|||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CreateSignature extends CreateSignatureBase {
|
||||||
|
public CreateSignature(KeyStore keystore, char[] pin)
|
||||||
|
throws KeyStoreException,
|
||||||
|
UnrecoverableKeyException,
|
||||||
|
NoSuchAlgorithmException,
|
||||||
|
IOException,
|
||||||
|
CertificateException {
|
||||||
|
super(keystore, pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@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",
|
||||||
description =
|
description =
|
||||||
"This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO")
|
"This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile pdf = request.getFileInput();
|
MultipartFile pdf = request.getFileInput();
|
||||||
@@ -80,6 +82,7 @@ public class CertSignController {
|
|||||||
MultipartFile privateKeyFile = request.getPrivateKeyFile();
|
MultipartFile privateKeyFile = request.getPrivateKeyFile();
|
||||||
MultipartFile certFile = request.getCertFile();
|
MultipartFile certFile = request.getCertFile();
|
||||||
MultipartFile p12File = request.getP12File();
|
MultipartFile p12File = request.getP12File();
|
||||||
|
MultipartFile jksfile = request.getJksFile();
|
||||||
String password = request.getPassword();
|
String password = request.getPassword();
|
||||||
Boolean showSignature = request.isShowSignature();
|
Boolean showSignature = request.isShowSignature();
|
||||||
String reason = request.getReason();
|
String reason = request.getReason();
|
||||||
@@ -87,203 +90,94 @@ public class CertSignController {
|
|||||||
String name = request.getName();
|
String name = request.getName();
|
||||||
Integer pageNumber = request.getPageNumber();
|
Integer pageNumber = request.getPageNumber();
|
||||||
|
|
||||||
PrivateKey privateKey = null;
|
if (certType == null) {
|
||||||
X509Certificate cert = null;
|
throw new IllegalArgumentException("Cert type must be provided");
|
||||||
|
|
||||||
if (certType != null) {
|
|
||||||
logger.info("Cert type provided: {}", certType);
|
|
||||||
switch (certType) {
|
|
||||||
case "PKCS12":
|
|
||||||
if (p12File != null) {
|
|
||||||
KeyStore ks = KeyStore.getInstance("PKCS12");
|
|
||||||
ks.load(
|
|
||||||
new ByteArrayInputStream(p12File.getBytes()),
|
|
||||||
password.toCharArray());
|
|
||||||
String alias = ks.aliases().nextElement();
|
|
||||||
if (!ks.isKeyEntry(alias)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"The provided PKCS12 file does not contain a private key.");
|
|
||||||
}
|
|
||||||
privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray());
|
|
||||||
cert = (X509Certificate) ks.getCertificate(alias);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "PEM":
|
|
||||||
if (privateKeyFile != null && certFile != null) {
|
|
||||||
// Load private key
|
|
||||||
KeyFactory keyFactory =
|
|
||||||
KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
|
|
||||||
if (isPEM(privateKeyFile.getBytes())) {
|
|
||||||
privateKey =
|
|
||||||
keyFactory.generatePrivate(
|
|
||||||
new PKCS8EncodedKeySpec(
|
|
||||||
parsePEM(privateKeyFile.getBytes())));
|
|
||||||
} else {
|
|
||||||
privateKey =
|
|
||||||
keyFactory.generatePrivate(
|
|
||||||
new PKCS8EncodedKeySpec(privateKeyFile.getBytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load certificate
|
|
||||||
CertificateFactory certFactory =
|
|
||||||
CertificateFactory.getInstance(
|
|
||||||
"X.509", BouncyCastleProvider.PROVIDER_NAME);
|
|
||||||
if (isPEM(certFile.getBytes())) {
|
|
||||||
cert =
|
|
||||||
(X509Certificate)
|
|
||||||
certFactory.generateCertificate(
|
|
||||||
new ByteArrayInputStream(
|
|
||||||
parsePEM(certFile.getBytes())));
|
|
||||||
} else {
|
|
||||||
cert =
|
|
||||||
(X509Certificate)
|
|
||||||
certFactory.generateCertificate(
|
|
||||||
new ByteArrayInputStream(certFile.getBytes()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
PDSignature signature = new PDSignature();
|
|
||||||
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
|
|
||||||
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_SHA1);
|
|
||||||
signature.setName(name);
|
|
||||||
signature.setLocation(location);
|
|
||||||
signature.setReason(reason);
|
|
||||||
signature.setSignDate(Calendar.getInstance());
|
|
||||||
|
|
||||||
// Load the PDF
|
KeyStore ks = null;
|
||||||
try (PDDocument document = PDDocument.load(pdf.getBytes())) {
|
|
||||||
logger.info("Successfully loaded the provided PDF");
|
|
||||||
SignatureOptions signatureOptions = new SignatureOptions();
|
|
||||||
|
|
||||||
// If you want to show the signature
|
switch (certType) {
|
||||||
|
case "PEM":
|
||||||
|
ks = KeyStore.getInstance("JKS");
|
||||||
|
ks.load(null);
|
||||||
|
PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);
|
||||||
|
Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes());
|
||||||
|
ks.setKeyEntry(
|
||||||
|
"alias", privateKey, password.toCharArray(), new Certificate[] {cert});
|
||||||
|
break;
|
||||||
|
case "PKCS12":
|
||||||
|
ks = KeyStore.getInstance("PKCS12");
|
||||||
|
ks.load(p12File.getInputStream(), password.toCharArray());
|
||||||
|
break;
|
||||||
|
case "JKS":
|
||||||
|
ks = KeyStore.getInstance("JKS");
|
||||||
|
ks.load(jksfile.getInputStream(), password.toCharArray());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid cert type: " + certType);
|
||||||
|
}
|
||||||
|
|
||||||
// ATTEMPT 2
|
// TODO: page number
|
||||||
if (showSignature != null && showSignature) {
|
|
||||||
PDPage page = document.getPage(pageNumber - 1);
|
|
||||||
|
|
||||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
|
||||||
if (acroForm == null) {
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
acroForm = new PDAcroForm(document);
|
sign(pdf.getBytes(), baos, createSignature, name, location, reason);
|
||||||
document.getDocumentCatalog().setAcroForm(acroForm);
|
return WebResponseUtils.boasToWebResponse(
|
||||||
}
|
baos, pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_signed.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new signature field and widget
|
private static void sign(
|
||||||
|
byte[] input,
|
||||||
|
OutputStream output,
|
||||||
|
CreateSignature instance,
|
||||||
|
String name,
|
||||||
|
String location,
|
||||||
|
String reason) {
|
||||||
|
try (PDDocument doc = Loader.loadPDF(input)) {
|
||||||
|
PDSignature signature = new PDSignature();
|
||||||
|
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
|
||||||
|
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
|
||||||
|
signature.setName(name);
|
||||||
|
signature.setLocation(location);
|
||||||
|
signature.setReason(reason);
|
||||||
|
signature.setSignDate(Calendar.getInstance());
|
||||||
|
|
||||||
PDSignatureField signatureField = new PDSignatureField(acroForm);
|
doc.addSignature(signature, instance);
|
||||||
PDAnnotationWidget widget = signatureField.getWidgets().get(0);
|
doc.saveIncremental(output);
|
||||||
PDRectangle rect =
|
|
||||||
new PDRectangle(100, 100, 200, 50); // Define the rectangle size here
|
|
||||||
widget.setRectangle(rect);
|
|
||||||
page.getAnnotations().add(widget);
|
|
||||||
|
|
||||||
// Set the appearance for the signature field
|
|
||||||
PDAppearanceDictionary appearanceDict = new PDAppearanceDictionary();
|
|
||||||
PDAppearanceStream appearanceStream = new PDAppearanceStream(document);
|
|
||||||
appearanceStream.setResources(new PDResources());
|
|
||||||
appearanceStream.setBBox(rect);
|
|
||||||
appearanceDict.setNormalAppearance(appearanceStream);
|
|
||||||
widget.setAppearance(appearanceDict);
|
|
||||||
|
|
||||||
try (PDPageContentStream contentStream =
|
|
||||||
new PDPageContentStream(document, appearanceStream)) {
|
|
||||||
contentStream.beginText();
|
|
||||||
contentStream.setFont(PDType1Font.HELVETICA_BOLD, 12);
|
|
||||||
contentStream.newLineAtOffset(110, 130);
|
|
||||||
contentStream.showText(
|
|
||||||
"Digitally signed by: " + (name != null ? name : "Unknown"));
|
|
||||||
contentStream.newLineAtOffset(0, -15);
|
|
||||||
contentStream.showText(
|
|
||||||
"Date: "
|
|
||||||
+ new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z")
|
|
||||||
.format(new Date()));
|
|
||||||
contentStream.newLineAtOffset(0, -15);
|
|
||||||
if (reason != null && !reason.isEmpty()) {
|
|
||||||
contentStream.showText("Reason: " + reason);
|
|
||||||
contentStream.newLineAtOffset(0, -15);
|
|
||||||
}
|
|
||||||
if (location != null && !location.isEmpty()) {
|
|
||||||
contentStream.showText("Location: " + location);
|
|
||||||
contentStream.newLineAtOffset(0, -15);
|
|
||||||
}
|
|
||||||
contentStream.endText();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the widget annotation to the page
|
|
||||||
page.getAnnotations().add(widget);
|
|
||||||
|
|
||||||
// Add the signature field to the acroform
|
|
||||||
acroForm.getFields().add(signatureField);
|
|
||||||
|
|
||||||
// Handle multiple signatures by ensuring a unique field name
|
|
||||||
String baseFieldName = "Signature";
|
|
||||||
String signatureFieldName = baseFieldName;
|
|
||||||
int suffix = 1;
|
|
||||||
while (acroForm.getField(signatureFieldName) != null) {
|
|
||||||
suffix++;
|
|
||||||
signatureFieldName = baseFieldName + suffix;
|
|
||||||
}
|
|
||||||
signatureField.setPartialName(signatureFieldName);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addSignature(signature, signatureOptions);
|
|
||||||
logger.info("Signature added to the PDF document");
|
|
||||||
// External signing
|
|
||||||
ExternalSigningSupport externalSigning =
|
|
||||||
document.saveIncrementalForExternalSigning(new ByteArrayOutputStream());
|
|
||||||
|
|
||||||
byte[] content = IOUtils.toByteArray(externalSigning.getContent());
|
|
||||||
|
|
||||||
// Using BouncyCastle to sign
|
|
||||||
CMSTypedData cmsData = new CMSProcessableByteArray(content);
|
|
||||||
|
|
||||||
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
|
||||||
ContentSigner signer =
|
|
||||||
new JcaContentSignerBuilder("SHA256withRSA")
|
|
||||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
|
||||||
.build(privateKey);
|
|
||||||
|
|
||||||
gen.addSignerInfoGenerator(
|
|
||||||
new JcaSignerInfoGeneratorBuilder(
|
|
||||||
new JcaDigestCalculatorProviderBuilder()
|
|
||||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
|
||||||
.build())
|
|
||||||
.build(signer, cert));
|
|
||||||
|
|
||||||
gen.addCertificates(new JcaCertStore(Collections.singletonList(cert)));
|
|
||||||
CMSSignedData signedData = gen.generate(cmsData, false);
|
|
||||||
|
|
||||||
byte[] cmsSignature = signedData.getEncoded();
|
|
||||||
logger.info("About to sign content using BouncyCastle");
|
|
||||||
externalSigning.setSignature(cmsSignature);
|
|
||||||
logger.info("Signature set successfully");
|
|
||||||
|
|
||||||
// After setting the signature, return the resultant PDF
|
|
||||||
try (ByteArrayOutputStream signedPdfOutput = new ByteArrayOutputStream()) {
|
|
||||||
document.save(signedPdfOutput);
|
|
||||||
return WebResponseUtils.boasToWebResponse(
|
|
||||||
signedPdfOutput,
|
|
||||||
pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_signed.pdf");
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] parsePEM(byte[] content) throws IOException {
|
private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
|
||||||
PemReader pemReader =
|
throws IOException, OperatorCreationException, PKCSException {
|
||||||
new PemReader(new InputStreamReader(new ByteArrayInputStream(content)));
|
try (PEMParser pemParser =
|
||||||
return pemReader.readPemObject().getContent();
|
new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
|
||||||
|
Object pemObject = pemParser.readObject();
|
||||||
|
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
|
||||||
|
PrivateKeyInfo pkInfo;
|
||||||
|
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
|
||||||
|
InputDecryptorProvider decProv =
|
||||||
|
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
|
||||||
|
pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv);
|
||||||
|
} else if (pemObject instanceof PEMEncryptedKeyPair) {
|
||||||
|
PEMDecryptorProvider decProv =
|
||||||
|
new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
|
||||||
|
pkInfo =
|
||||||
|
((PEMEncryptedKeyPair) pemObject)
|
||||||
|
.decryptKeyPair(decProv)
|
||||||
|
.getPrivateKeyInfo();
|
||||||
|
} else {
|
||||||
|
pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
|
||||||
|
}
|
||||||
|
return converter.getPrivateKey(pkInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPEM(byte[] content) {
|
private Certificate getCertificateFromPEM(byte[] pemBytes)
|
||||||
String contentStr = new String(content);
|
throws IOException, CertificateException {
|
||||||
return contentStr.contains("-----BEGIN") && contentStr.contains("-----END");
|
try (ByteArrayInputStream bis = new ByteArrayInputStream(pemBytes)) {
|
||||||
|
return CertificateFactory.getInstance("X.509").generateCertificate(bis);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,9 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.pdfbox.cos.COSDocument;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.cos.COSInputStream;
|
import org.apache.pdfbox.cos.COSInputStream;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.cos.COSObject;
|
|
||||||
import org.apache.pdfbox.cos.COSStream;
|
|
||||||
import org.apache.pdfbox.cos.COSString;
|
import org.apache.pdfbox.cos.COSString;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
@@ -87,7 +85,7 @@ public class GetInfoOnPDF {
|
|||||||
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
|
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
|
||||||
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
|
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
try (PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream()); ) {
|
try (PDDocument pdfBoxDoc = Loader.loadPDF(inputFile.getBytes()); ) {
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
ObjectNode jsonOutput = objectMapper.createObjectNode();
|
ObjectNode jsonOutput = objectMapper.createObjectNode();
|
||||||
|
|
||||||
@@ -129,17 +127,6 @@ public class GetInfoOnPDF {
|
|||||||
boolean hasCompression = false;
|
boolean hasCompression = false;
|
||||||
String compressionType = "None";
|
String compressionType = "None";
|
||||||
|
|
||||||
COSDocument cosDoc = pdfBoxDoc.getDocument();
|
|
||||||
for (COSObject cosObject : cosDoc.getObjects()) {
|
|
||||||
if (cosObject.getObject() instanceof COSStream) {
|
|
||||||
COSStream cosStream = (COSStream) cosObject.getObject();
|
|
||||||
if (COSName.OBJ_STM.equals(cosStream.getItem(COSName.TYPE))) {
|
|
||||||
hasCompression = true;
|
|
||||||
compressionType = "Object Streams";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
basicInfo.put("Compression", hasCompression);
|
basicInfo.put("Compression", hasCompression);
|
||||||
if (hasCompression) basicInfo.put("CompressionType", compressionType);
|
if (hasCompression) basicInfo.put("CompressionType", compressionType);
|
||||||
|
|
||||||
@@ -343,7 +330,6 @@ public class GetInfoOnPDF {
|
|||||||
permissionsNode.put("CanModify", ap.canModify());
|
permissionsNode.put("CanModify", ap.canModify());
|
||||||
permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations());
|
permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations());
|
||||||
permissionsNode.put("CanPrint", ap.canPrint());
|
permissionsNode.put("CanPrint", ap.canPrint());
|
||||||
permissionsNode.put("CanPrintDegraded", ap.canPrintDegraded());
|
|
||||||
|
|
||||||
encryption.set(
|
encryption.set(
|
||||||
"Permissions", permissionsNode); // set the node under "Permissions"
|
"Permissions", permissionsNode); // set the node under "Permissions"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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;
|
||||||
@@ -38,7 +39,7 @@ public class PasswordController {
|
|||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
String password = request.getPassword();
|
String password = request.getPassword();
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
|
PDDocument document = Loader.loadPDF(fileInput.getBytes(), password);
|
||||||
document.setAllSecurityToBeRemoved(true);
|
document.setAllSecurityToBeRemoved(true);
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
@@ -66,7 +67,7 @@ public class PasswordController {
|
|||||||
boolean canPrint = request.isCanPrint();
|
boolean canPrint = request.isCanPrint();
|
||||||
boolean canPrintFaithful = request.isCanPrintFaithful();
|
boolean canPrintFaithful = request.isCanPrintFaithful();
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(fileInput.getBytes());
|
PDDocument document = Loader.loadPDF(fileInput.getBytes());
|
||||||
AccessPermission ap = new AccessPermission();
|
AccessPermission ap = new AccessPermission();
|
||||||
ap.setCanAssembleDocument(!canAssembleDocument);
|
ap.setCanAssembleDocument(!canAssembleDocument);
|
||||||
ap.setCanExtractContent(!canExtractContent);
|
ap.setCanExtractContent(!canExtractContent);
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ package stirling.software.SPDF.controller.api.security;
|
|||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import 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;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
@@ -57,7 +58,7 @@ public class RedactController {
|
|||||||
System.out.println(listOfTextString);
|
System.out.println(listOfTextString);
|
||||||
String[] listOfText = listOfTextString.split("\n");
|
String[] listOfText = listOfTextString.split("\n");
|
||||||
byte[] bytes = file.getBytes();
|
byte[] bytes = file.getBytes();
|
||||||
PDDocument document = PDDocument.load(new ByteArrayInputStream(bytes));
|
PDDocument document = Loader.loadPDF(bytes);
|
||||||
|
|
||||||
Color redactColor;
|
Color redactColor;
|
||||||
try {
|
try {
|
||||||
@@ -86,7 +87,9 @@ public class RedactController {
|
|||||||
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
|
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
|
||||||
imageDocument.addPage(newPage);
|
imageDocument.addPage(newPage);
|
||||||
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
|
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
|
||||||
PDPageContentStream contentStream = new PDPageContentStream(imageDocument, newPage);
|
PDPageContentStream contentStream =
|
||||||
|
new PDPageContentStream(
|
||||||
|
imageDocument, newPage, AppendMode.APPEND, true, true);
|
||||||
contentStream.drawImage(pdImage, 0, 0);
|
contentStream.drawImage(pdImage, 0, 0);
|
||||||
contentStream.close();
|
contentStream.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,171 +1,172 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.cos.COSDictionary;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSDictionary;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDResources;
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
|
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/security")
|
@RestController
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@RequestMapping("/api/v1/security")
|
||||||
public class SanitizeController {
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
|
public class SanitizeController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
|
||||||
@Operation(
|
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
||||||
summary = "Sanitize a PDF file",
|
@Operation(
|
||||||
description =
|
summary = "Sanitize a PDF file",
|
||||||
"This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
description =
|
||||||
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
|
"This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
||||||
throws IOException {
|
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
|
||||||
MultipartFile inputFile = request.getFileInput();
|
throws IOException {
|
||||||
boolean removeJavaScript = request.isRemoveJavaScript();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();
|
boolean removeJavaScript = request.isRemoveJavaScript();
|
||||||
boolean removeMetadata = request.isRemoveMetadata();
|
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();
|
||||||
boolean removeLinks = request.isRemoveLinks();
|
boolean removeMetadata = request.isRemoveMetadata();
|
||||||
boolean removeFonts = request.isRemoveFonts();
|
boolean removeLinks = request.isRemoveLinks();
|
||||||
|
boolean removeFonts = request.isRemoveFonts();
|
||||||
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
|
||||||
if (removeJavaScript) {
|
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) {
|
||||||
sanitizeJavaScript(document);
|
if (removeJavaScript) {
|
||||||
}
|
sanitizeJavaScript(document);
|
||||||
|
}
|
||||||
if (removeEmbeddedFiles) {
|
|
||||||
sanitizeEmbeddedFiles(document);
|
if (removeEmbeddedFiles) {
|
||||||
}
|
sanitizeEmbeddedFiles(document);
|
||||||
|
}
|
||||||
if (removeMetadata) {
|
|
||||||
sanitizeMetadata(document);
|
if (removeMetadata) {
|
||||||
}
|
sanitizeMetadata(document);
|
||||||
|
}
|
||||||
if (removeLinks) {
|
|
||||||
sanitizeLinks(document);
|
if (removeLinks) {
|
||||||
}
|
sanitizeLinks(document);
|
||||||
|
}
|
||||||
if (removeFonts) {
|
|
||||||
sanitizeFonts(document);
|
if (removeFonts) {
|
||||||
}
|
sanitizeFonts(document);
|
||||||
|
}
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
|
||||||
document,
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
document,
|
||||||
+ "_sanitized.pdf");
|
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
||||||
}
|
+ "_sanitized.pdf");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void sanitizeJavaScript(PDDocument document) throws IOException {
|
|
||||||
// Get the root dictionary (catalog) of the PDF
|
private void sanitizeJavaScript(PDDocument document) throws IOException {
|
||||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
// Get the root dictionary (catalog) of the PDF
|
||||||
|
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||||
// Get the Names dictionary
|
|
||||||
COSDictionary namesDict =
|
// Get the Names dictionary
|
||||||
(COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES);
|
COSDictionary namesDict =
|
||||||
|
(COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES);
|
||||||
if (namesDict != null) {
|
|
||||||
// Get the JavaScript dictionary
|
if (namesDict != null) {
|
||||||
COSDictionary javaScriptDict =
|
// Get the JavaScript dictionary
|
||||||
(COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript"));
|
COSDictionary javaScriptDict =
|
||||||
|
(COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript"));
|
||||||
if (javaScriptDict != null) {
|
|
||||||
// Remove the JavaScript dictionary
|
if (javaScriptDict != null) {
|
||||||
namesDict.removeItem(COSName.getPDFName("JavaScript"));
|
// Remove the JavaScript dictionary
|
||||||
}
|
namesDict.removeItem(COSName.getPDFName("JavaScript"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (PDPage page : document.getPages()) {
|
|
||||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
for (PDPage page : document.getPages()) {
|
||||||
if (annotation instanceof PDAnnotationWidget) {
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
PDAnnotationWidget widget = (PDAnnotationWidget) annotation;
|
if (annotation instanceof PDAnnotationWidget) {
|
||||||
PDAction action = widget.getAction();
|
PDAnnotationWidget widget = (PDAnnotationWidget) annotation;
|
||||||
if (action instanceof PDActionJavaScript) {
|
PDAction action = widget.getAction();
|
||||||
widget.setAction(null);
|
if (action instanceof PDActionJavaScript) {
|
||||||
}
|
widget.setAction(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
}
|
||||||
if (acroForm != null) {
|
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
||||||
for (PDField field : acroForm.getFields()) {
|
if (acroForm != null) {
|
||||||
PDFormFieldAdditionalActions actions = field.getActions();
|
for (PDField field : acroForm.getFields()) {
|
||||||
if (actions != null) {
|
PDFormFieldAdditionalActions actions = field.getActions();
|
||||||
if (actions.getC() instanceof PDActionJavaScript) {
|
if (actions != null) {
|
||||||
actions.setC(null);
|
if (actions.getC() instanceof PDActionJavaScript) {
|
||||||
}
|
actions.setC(null);
|
||||||
if (actions.getF() instanceof PDActionJavaScript) {
|
}
|
||||||
actions.setF(null);
|
if (actions.getF() instanceof PDActionJavaScript) {
|
||||||
}
|
actions.setF(null);
|
||||||
if (actions.getK() instanceof PDActionJavaScript) {
|
}
|
||||||
actions.setK(null);
|
if (actions.getK() instanceof PDActionJavaScript) {
|
||||||
}
|
actions.setK(null);
|
||||||
if (actions.getV() instanceof PDActionJavaScript) {
|
}
|
||||||
actions.setV(null);
|
if (actions.getV() instanceof PDActionJavaScript) {
|
||||||
}
|
actions.setV(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void sanitizeEmbeddedFiles(PDDocument document) {
|
|
||||||
PDPageTree allPages = document.getPages();
|
private void sanitizeEmbeddedFiles(PDDocument document) {
|
||||||
|
PDPageTree allPages = document.getPages();
|
||||||
for (PDPage page : allPages) {
|
|
||||||
PDResources res = page.getResources();
|
for (PDPage page : allPages) {
|
||||||
|
PDResources res = page.getResources();
|
||||||
// Remove embedded files from the PDF
|
|
||||||
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
|
// Remove embedded files from the PDF
|
||||||
}
|
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void sanitizeMetadata(PDDocument document) {
|
|
||||||
PDMetadata metadata = document.getDocumentCatalog().getMetadata();
|
private void sanitizeMetadata(PDDocument document) {
|
||||||
if (metadata != null) {
|
PDMetadata metadata = document.getDocumentCatalog().getMetadata();
|
||||||
document.getDocumentCatalog().setMetadata(null);
|
if (metadata != null) {
|
||||||
}
|
document.getDocumentCatalog().setMetadata(null);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void sanitizeLinks(PDDocument document) throws IOException {
|
|
||||||
for (PDPage page : document.getPages()) {
|
private void sanitizeLinks(PDDocument document) throws IOException {
|
||||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
for (PDPage page : document.getPages()) {
|
||||||
if (annotation instanceof PDAnnotationLink) {
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
PDAction action = ((PDAnnotationLink) annotation).getAction();
|
if (annotation instanceof PDAnnotationLink) {
|
||||||
if (action instanceof PDActionLaunch || action instanceof PDActionURI) {
|
PDAction action = ((PDAnnotationLink) annotation).getAction();
|
||||||
((PDAnnotationLink) annotation).setAction(null);
|
if (action instanceof PDActionLaunch || action instanceof PDActionURI) {
|
||||||
}
|
((PDAnnotationLink) annotation).setAction(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void sanitizeFonts(PDDocument document) {
|
|
||||||
for (PDPage page : document.getPages()) {
|
private void sanitizeFonts(PDDocument document) {
|
||||||
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
|
for (PDPage page : document.getPages()) {
|
||||||
}
|
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ import java.io.InputStream;
|
|||||||
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;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||||
@@ -58,7 +60,7 @@ public class WatermarkController {
|
|||||||
int heightSpacer = request.getHeightSpacer();
|
int heightSpacer = request.getHeightSpacer();
|
||||||
|
|
||||||
// Load the input PDF
|
// Load the input PDF
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
|
||||||
|
|
||||||
// Create a page in the document
|
// Create a page in the document
|
||||||
for (PDPage page : document.getPages()) {
|
for (PDPage page : document.getPages()) {
|
||||||
@@ -66,7 +68,7 @@ public class WatermarkController {
|
|||||||
// Get the page's content stream
|
// Get the page's content stream
|
||||||
PDPageContentStream contentStream =
|
PDPageContentStream contentStream =
|
||||||
new PDPageContentStream(
|
new PDPageContentStream(
|
||||||
document, page, PDPageContentStream.AppendMode.APPEND, true);
|
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
|
|
||||||
// Set transparency
|
// Set transparency
|
||||||
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
|
||||||
@@ -117,7 +119,7 @@ public class WatermarkController {
|
|||||||
String alphabet)
|
String alphabet)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String resourceDir = "";
|
String resourceDir = "";
|
||||||
PDFont font = PDType1Font.HELVETICA_BOLD;
|
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||||
switch (alphabet) {
|
switch (alphabet) {
|
||||||
case "arabic":
|
case "arabic":
|
||||||
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
|
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
|
||||||
|
|||||||
@@ -1,113 +1,130 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@Controller
|
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Controller
|
||||||
public class ConverterWebController {
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
public class ConverterWebController {
|
||||||
@GetMapping("/img-to-pdf")
|
|
||||||
@Hidden
|
@ConditionalOnExpression("#{bookFormatsInstalled}")
|
||||||
public String convertImgToPdfForm(Model model) {
|
@GetMapping("/book-to-pdf")
|
||||||
model.addAttribute("currentPage", "img-to-pdf");
|
@Hidden
|
||||||
return "convert/img-to-pdf";
|
public String convertBookToPdfForm(Model model) {
|
||||||
}
|
model.addAttribute("currentPage", "book-to-pdf");
|
||||||
|
return "convert/book-to-pdf";
|
||||||
@GetMapping("/html-to-pdf")
|
}
|
||||||
@Hidden
|
|
||||||
public String convertHTMLToPdfForm(Model model) {
|
@ConditionalOnExpression("#{bookFormatsInstalled}")
|
||||||
model.addAttribute("currentPage", "html-to-pdf");
|
@GetMapping("/pdf-to-book")
|
||||||
return "convert/html-to-pdf";
|
@Hidden
|
||||||
}
|
public String convertPdfToBookForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "pdf-to-book");
|
||||||
@GetMapping("/markdown-to-pdf")
|
return "convert/pdf-to-book";
|
||||||
@Hidden
|
}
|
||||||
public String convertMarkdownToPdfForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "markdown-to-pdf");
|
@GetMapping("/img-to-pdf")
|
||||||
return "convert/markdown-to-pdf";
|
@Hidden
|
||||||
}
|
public String convertImgToPdfForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "img-to-pdf");
|
||||||
@GetMapping("/url-to-pdf")
|
return "convert/img-to-pdf";
|
||||||
@Hidden
|
}
|
||||||
public String convertURLToPdfForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "url-to-pdf");
|
@GetMapping("/html-to-pdf")
|
||||||
return "convert/url-to-pdf";
|
@Hidden
|
||||||
}
|
public String convertHTMLToPdfForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "html-to-pdf");
|
||||||
@GetMapping("/pdf-to-img")
|
return "convert/html-to-pdf";
|
||||||
@Hidden
|
}
|
||||||
public String pdfToimgForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "pdf-to-img");
|
@GetMapping("/markdown-to-pdf")
|
||||||
return "convert/pdf-to-img";
|
@Hidden
|
||||||
}
|
public String convertMarkdownToPdfForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "markdown-to-pdf");
|
||||||
@GetMapping("/file-to-pdf")
|
return "convert/markdown-to-pdf";
|
||||||
@Hidden
|
}
|
||||||
public String convertToPdfForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "file-to-pdf");
|
@GetMapping("/url-to-pdf")
|
||||||
return "convert/file-to-pdf";
|
@Hidden
|
||||||
}
|
public String convertURLToPdfForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "url-to-pdf");
|
||||||
// PDF TO......
|
return "convert/url-to-pdf";
|
||||||
|
}
|
||||||
@GetMapping("/pdf-to-html")
|
|
||||||
@Hidden
|
@GetMapping("/pdf-to-img")
|
||||||
public ModelAndView pdfToHTML() {
|
@Hidden
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html");
|
public String pdfToimgForm(Model model) {
|
||||||
modelAndView.addObject("currentPage", "pdf-to-html");
|
model.addAttribute("currentPage", "pdf-to-img");
|
||||||
return modelAndView;
|
return "convert/pdf-to-img";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/pdf-to-presentation")
|
@GetMapping("/file-to-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public ModelAndView pdfToPresentation() {
|
public String convertToPdfForm(Model model) {
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation");
|
model.addAttribute("currentPage", "file-to-pdf");
|
||||||
modelAndView.addObject("currentPage", "pdf-to-presentation");
|
return "convert/file-to-pdf";
|
||||||
return modelAndView;
|
}
|
||||||
}
|
|
||||||
|
// PDF TO......
|
||||||
@GetMapping("/pdf-to-text")
|
|
||||||
@Hidden
|
@GetMapping("/pdf-to-html")
|
||||||
public ModelAndView pdfToText() {
|
@Hidden
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text");
|
public ModelAndView pdfToHTML() {
|
||||||
modelAndView.addObject("currentPage", "pdf-to-text");
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-html");
|
||||||
return modelAndView;
|
modelAndView.addObject("currentPage", "pdf-to-html");
|
||||||
}
|
return modelAndView;
|
||||||
|
}
|
||||||
@GetMapping("/pdf-to-word")
|
|
||||||
@Hidden
|
@GetMapping("/pdf-to-presentation")
|
||||||
public ModelAndView pdfToWord() {
|
@Hidden
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word");
|
public ModelAndView pdfToPresentation() {
|
||||||
modelAndView.addObject("currentPage", "pdf-to-word");
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-presentation");
|
||||||
return modelAndView;
|
modelAndView.addObject("currentPage", "pdf-to-presentation");
|
||||||
}
|
return modelAndView;
|
||||||
|
}
|
||||||
@GetMapping("/pdf-to-xml")
|
|
||||||
@Hidden
|
@GetMapping("/pdf-to-text")
|
||||||
public ModelAndView pdfToXML() {
|
@Hidden
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml");
|
public ModelAndView pdfToText() {
|
||||||
modelAndView.addObject("currentPage", "pdf-to-xml");
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-text");
|
||||||
return modelAndView;
|
modelAndView.addObject("currentPage", "pdf-to-text");
|
||||||
}
|
return modelAndView;
|
||||||
|
}
|
||||||
@GetMapping("/pdf-to-csv")
|
|
||||||
@Hidden
|
@GetMapping("/pdf-to-word")
|
||||||
public ModelAndView pdfToCSV() {
|
@Hidden
|
||||||
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv");
|
public ModelAndView pdfToWord() {
|
||||||
modelAndView.addObject("currentPage", "pdf-to-csv");
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-word");
|
||||||
return modelAndView;
|
modelAndView.addObject("currentPage", "pdf-to-word");
|
||||||
}
|
return modelAndView;
|
||||||
|
}
|
||||||
@GetMapping("/pdf-to-pdfa")
|
|
||||||
@Hidden
|
@GetMapping("/pdf-to-xml")
|
||||||
public String pdfToPdfAForm(Model model) {
|
@Hidden
|
||||||
model.addAttribute("currentPage", "pdf-to-pdfa");
|
public ModelAndView pdfToXML() {
|
||||||
return "convert/pdf-to-pdfa";
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-xml");
|
||||||
}
|
modelAndView.addObject("currentPage", "pdf-to-xml");
|
||||||
}
|
return modelAndView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pdf-to-csv")
|
||||||
|
@Hidden
|
||||||
|
public ModelAndView pdfToCSV() {
|
||||||
|
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv");
|
||||||
|
modelAndView.addObject("currentPage", "pdf-to-csv");
|
||||||
|
return modelAndView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pdf-to-pdfa")
|
||||||
|
@Hidden
|
||||||
|
public String pdfToPdfAForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "pdf-to-pdfa");
|
||||||
|
return "convert/pdf-to-pdfa";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,6 +59,14 @@ public class GeneralWebController {
|
|||||||
.readValue(config, new TypeReference<Map<String, Object>>() {});
|
.readValue(config, new TypeReference<Map<String, Object>>() {});
|
||||||
|
|
||||||
String name = (String) jsonContent.get("name");
|
String name = (String) jsonContent.get("name");
|
||||||
|
if (name == null || name.length() < 1) {
|
||||||
|
String filename =
|
||||||
|
jsonFiles
|
||||||
|
.get(pipelineConfigs.indexOf(config))
|
||||||
|
.getFileName()
|
||||||
|
.toString();
|
||||||
|
name = filename.substring(0, filename.lastIndexOf('.'));
|
||||||
|
}
|
||||||
Map<String, String> configWithName = new HashMap<>();
|
Map<String, String> configWithName = new HashMap<>();
|
||||||
configWithName.put("json", config);
|
configWithName.put("json", config);
|
||||||
configWithName.put("name", name);
|
configWithName.put("name", name);
|
||||||
|
|||||||
@@ -1,15 +1,27 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.Dependency;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class HomeWebController {
|
public class HomeWebController {
|
||||||
@@ -21,6 +33,24 @@ public class HomeWebController {
|
|||||||
return "about";
|
return "about";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/licenses")
|
||||||
|
@Hidden
|
||||||
|
public String licensesForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "licenses");
|
||||||
|
Resource resource = new ClassPathResource("static/3rdPartyLicenses.json");
|
||||||
|
try {
|
||||||
|
InputStream is = resource.getInputStream();
|
||||||
|
String json = new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
Map<String, List<Dependency>> data =
|
||||||
|
mapper.readValue(json, new TypeReference<Map<String, List<Dependency>>>() {});
|
||||||
|
model.addAttribute("dependencies", data.get("dependencies"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return "licenses";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public String home(Model model) {
|
public String home(Model model) {
|
||||||
model.addAttribute("currentPage", "home");
|
model.addAttribute("currentPage", "home");
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ public class OtherWebController {
|
|||||||
return "misc/show-javascript";
|
return "misc/show-javascript";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/stamp")
|
||||||
|
@Hidden
|
||||||
|
public String stampForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "stamp");
|
||||||
|
return "misc/stamp";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/add-page-numbers")
|
@GetMapping("/add-page-numbers")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String addPageNumbersForm(Model model) {
|
public String addPageNumbersForm(Model model) {
|
||||||
|
|||||||
@@ -1,69 +1,69 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class SecurityWebController {
|
public class SecurityWebController {
|
||||||
|
|
||||||
@GetMapping("/auto-redact")
|
@GetMapping("/auto-redact")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String autoRedactForm(Model model) {
|
public String autoRedactForm(Model model) {
|
||||||
model.addAttribute("currentPage", "auto-redact");
|
model.addAttribute("currentPage", "auto-redact");
|
||||||
return "security/auto-redact";
|
return "security/auto-redact";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/add-password")
|
@GetMapping("/add-password")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String addPasswordForm(Model model) {
|
public String addPasswordForm(Model model) {
|
||||||
model.addAttribute("currentPage", "add-password");
|
model.addAttribute("currentPage", "add-password");
|
||||||
return "security/add-password";
|
return "security/add-password";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/change-permissions")
|
@GetMapping("/change-permissions")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String permissionsForm(Model model) {
|
public String permissionsForm(Model model) {
|
||||||
model.addAttribute("currentPage", "change-permissions");
|
model.addAttribute("currentPage", "change-permissions");
|
||||||
return "security/change-permissions";
|
return "security/change-permissions";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/remove-password")
|
@GetMapping("/remove-password")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String removePasswordForm(Model model) {
|
public String removePasswordForm(Model model) {
|
||||||
model.addAttribute("currentPage", "remove-password");
|
model.addAttribute("currentPage", "remove-password");
|
||||||
return "security/remove-password";
|
return "security/remove-password";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/add-watermark")
|
@GetMapping("/add-watermark")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String addWatermarkForm(Model model) {
|
public String addWatermarkForm(Model model) {
|
||||||
model.addAttribute("currentPage", "add-watermark");
|
model.addAttribute("currentPage", "add-watermark");
|
||||||
return "security/add-watermark";
|
return "security/add-watermark";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/cert-sign")
|
@GetMapping("/cert-sign")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String certSignForm(Model model) {
|
public String certSignForm(Model model) {
|
||||||
model.addAttribute("currentPage", "cert-sign");
|
model.addAttribute("currentPage", "cert-sign");
|
||||||
return "security/cert-sign";
|
return "security/cert-sign";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/sanitize-pdf")
|
@GetMapping("/sanitize-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String sanitizeForm(Model model) {
|
public String sanitizeForm(Model model) {
|
||||||
model.addAttribute("currentPage", "sanitize-pdf");
|
model.addAttribute("currentPage", "sanitize-pdf");
|
||||||
return "security/sanitize-pdf";
|
return "security/sanitize-pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/get-info-on-pdf")
|
@GetMapping("/get-info-on-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String getInfo(Model model) {
|
public String getInfo(Model model) {
|
||||||
model.addAttribute("currentPage", "get-info-on-pdf");
|
model.addAttribute("currentPage", "get-info-on-pdf");
|
||||||
return "security/get-info-on-pdf";
|
return "security/get-info-on-pdf";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ public class ApplicationProperties {
|
|||||||
private String rootURIPath;
|
private String rootURIPath;
|
||||||
private String customStaticFilePath;
|
private String customStaticFilePath;
|
||||||
private Integer maxFileSize;
|
private Integer maxFileSize;
|
||||||
|
private CustomApplications customApplications;
|
||||||
|
|
||||||
private Boolean enableAlphaFunctionality;
|
private Boolean enableAlphaFunctionality;
|
||||||
|
|
||||||
@@ -261,6 +262,14 @@ public class ApplicationProperties {
|
|||||||
this.maxFileSize = maxFileSize;
|
this.maxFileSize = maxFileSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CustomApplications getCustomApplications() {
|
||||||
|
return customApplications != null ? customApplications : new CustomApplications();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomApplications(CustomApplications customApplications) {
|
||||||
|
this.customApplications = customApplications;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "System [defaultLocale="
|
return "System [defaultLocale="
|
||||||
@@ -273,10 +282,42 @@ public class ApplicationProperties {
|
|||||||
+ customStaticFilePath
|
+ customStaticFilePath
|
||||||
+ ", maxFileSize="
|
+ ", maxFileSize="
|
||||||
+ maxFileSize
|
+ maxFileSize
|
||||||
|
+ ", customApplications="
|
||||||
|
+ customApplications
|
||||||
+ ", enableAlphaFunctionality="
|
+ ", enableAlphaFunctionality="
|
||||||
+ enableAlphaFunctionality
|
+ enableAlphaFunctionality
|
||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class CustomApplications {
|
||||||
|
private boolean installBookFormats;
|
||||||
|
private boolean installAdvancedHtmlToPDF;
|
||||||
|
|
||||||
|
public boolean isInstallBookFormats() {
|
||||||
|
return installBookFormats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstallBookFormats(boolean installBookFormats) {
|
||||||
|
this.installBookFormats = installBookFormats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInstallAdvancedHtmlToPDF() {
|
||||||
|
return installAdvancedHtmlToPDF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstallAdvancedHtmlToPDF(boolean installAdvancedHtmlToPDF) {
|
||||||
|
this.installAdvancedHtmlToPDF = installAdvancedHtmlToPDF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CustomApplications [installBookFormats="
|
||||||
|
+ installBookFormats
|
||||||
|
+ ", installAdvancedHtmlToPDF="
|
||||||
|
+ installAdvancedHtmlToPDF
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Ui {
|
public static class Ui {
|
||||||
|
|||||||
12
src/main/java/stirling/software/SPDF/model/Dependency.java
Normal file
12
src/main/java/stirling/software/SPDF/model/Dependency.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Dependency {
|
||||||
|
private String moduleName;
|
||||||
|
private String moduleUrl;
|
||||||
|
private String moduleVersion;
|
||||||
|
private String moduleLicense;
|
||||||
|
private String moduleLicenseUrl;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package stirling.software.SPDF.model.api;
|
|||||||
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 io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
@@ -27,7 +28,7 @@ public class PDFWithPageNums extends PDFFile {
|
|||||||
public List<Integer> getPageNumbersList() {
|
public List<Integer> getPageNumbersList() {
|
||||||
int pageCount = 0;
|
int pageCount = 0;
|
||||||
try {
|
try {
|
||||||
pageCount = PDDocument.load(getFileInput().getInputStream()).getNumberOfPages();
|
pageCount = Loader.loadPDF(getFileInput().getBytes()).getNumberOfPages();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO Auto-generated catch block
|
// TODO Auto-generated catch block
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package stirling.software.SPDF.model.api.converters;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class HTMLToPdfRequest extends PDFFile {
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Zoom level for displaying the website. Default is '1'.",
|
||||||
|
defaultValue = "1")
|
||||||
|
private float zoom;
|
||||||
|
|
||||||
|
@Schema(description = "Width of the page in centimeters.")
|
||||||
|
private Float pageWidth;
|
||||||
|
|
||||||
|
@Schema(description = "Height of the page in centimeters.")
|
||||||
|
private Float pageHeight;
|
||||||
|
|
||||||
|
@Schema(description = "Top margin of the page in millimeters.")
|
||||||
|
private Float marginTop;
|
||||||
|
|
||||||
|
@Schema(description = "Bottom margin of the page in millimeters.")
|
||||||
|
private Float marginBottom;
|
||||||
|
|
||||||
|
@Schema(description = "Left margin of the page in millimeters.")
|
||||||
|
private Float marginLeft;
|
||||||
|
|
||||||
|
@Schema(description = "Right margin of the page in millimeters.")
|
||||||
|
private Float marginRight;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Enable or disable rendering of website background.",
|
||||||
|
allowableValues = {"Yes", "No"})
|
||||||
|
private String printBackground;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"Enable or disable the default header. The default header includes the name of the page on the left and the page number on the right.",
|
||||||
|
allowableValues = {"Yes", "No"})
|
||||||
|
private String defaultHeader;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Change the CSS media type of the page. Defaults to 'print'.",
|
||||||
|
allowableValues = {"none", "print", "screen"},
|
||||||
|
defaultValue = "print")
|
||||||
|
private String cssMediaType;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package stirling.software.SPDF.model.api.converters;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class PdfToBookRequest extends PDFFile {
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "The output Ebook format",
|
||||||
|
allowableValues = {
|
||||||
|
"epub", "mobi", "azw3", "docx", "rtf", "txt", "html", "lit", "fb2", "pdb", "lrf"
|
||||||
|
})
|
||||||
|
private String outputFormat;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package stirling.software.SPDF.model.api.misc;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class AddStampRequest extends PDFFile {
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "The stamp type (text or image)",
|
||||||
|
allowableValues = {"text", "image"},
|
||||||
|
required = true)
|
||||||
|
private String stampType;
|
||||||
|
|
||||||
|
@Schema(description = "The stamp text")
|
||||||
|
private String stampText;
|
||||||
|
|
||||||
|
@Schema(description = "The stamp image")
|
||||||
|
private MultipartFile stampImage;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "The selected alphabet",
|
||||||
|
allowableValues = {"roman", "arabic", "japanese", "korean", "chinese"},
|
||||||
|
defaultValue = "roman")
|
||||||
|
private String alphabet = "roman";
|
||||||
|
|
||||||
|
@Schema(description = "The font size of the stamp text", example = "30")
|
||||||
|
private float fontSize = 30;
|
||||||
|
|
||||||
|
@Schema(description = "The rotation of the stamp in degrees", example = "0")
|
||||||
|
private float rotation = 0;
|
||||||
|
|
||||||
|
@Schema(description = "The opacity of the stamp (0.0 - 1.0)", example = "0.5")
|
||||||
|
private float opacity;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"Position for stamp placement based on a 1-9 grid (1: bottom-left, 2: bottom-center, ..., 9: top-right)",
|
||||||
|
example = "1")
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"Override X coordinate for stamp placement. If set, it will override the position-based calculation. Negative value means no override.",
|
||||||
|
example = "-1")
|
||||||
|
private float overrideX = -1; // Default to -1 indicating no override
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"Override Y coordinate for stamp placement. If set, it will override the position-based calculation. Negative value means no override.",
|
||||||
|
example = "-1")
|
||||||
|
private float overrideY = -1; // Default to -1 indicating no override
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Specifies the margin size for the stamp.",
|
||||||
|
allowableValues = {"small", "medium", "large", "x-large"},
|
||||||
|
defaultValue = "medium")
|
||||||
|
private String customMargin = "medium";
|
||||||
|
|
||||||
|
@Schema(description = "The color for stamp", defaultValue = "#d3d3d3")
|
||||||
|
private String customColor = "#d3d3d3";
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user