Compare commits
113 Commits
add-elemen
...
v0.19.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||||
@@ -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
|
||||||
|
```
|
||||||
|
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,9 +8,9 @@ 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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
```
|
||||||
|
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.
|
||||||
40
README.md
40
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.
|
||||||
|
|
||||||
@@ -22,10 +22,10 @@ Please feel free to submit feature requests or report bugs either through GitHub
|
|||||||
|
|
||||||
## 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**
|
||||||
@@ -80,29 +80,29 @@ Please feel free to submit feature requests or report bugs either through GitHub
|
|||||||
- 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
|
Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) hosted by the team at adminforge.de
|
||||||
|
|
||||||
## 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.
|
||||||

|

|
||||||

|

|
||||||
@@ -144,17 +144,18 @@ 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?
|
## Want to add your own language?
|
||||||
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,9 +171,12 @@ 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
|
If you want to add your own language to Stirling-PDF please refer
|
||||||
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
|
https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md
|
||||||
|
|
||||||
And please create a PR to merge it back in so others can use it!
|
And please create a PR to merge it back in so others can use it!
|
||||||
|
|
||||||
@@ -224,7 +228,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,7 +238,7 @@ 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
|
||||||
|
|||||||
29
build.gradle
29
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.19.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:2.0.30'){
|
||||||
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:2.0.30'){
|
||||||
exclude group: 'commons-logging', module: 'commons-logging'
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,81 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import java.nio.file.Files;
|
||||||
import org.springframework.context.annotation.Bean;
|
import java.nio.file.Paths;
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
@Configuration
|
|
||||||
public class AppConfig {
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Configuration
|
||||||
|
public class AppConfig {
|
||||||
@Bean(name = "loginEnabled")
|
|
||||||
public boolean loginEnabled() {
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
return applicationProperties.getSecurity().getEnableLogin();
|
|
||||||
}
|
@Bean(name = "loginEnabled")
|
||||||
|
public boolean loginEnabled() {
|
||||||
@Bean(name = "appName")
|
return applicationProperties.getSecurity().getEnableLogin();
|
||||||
public String appName() {
|
}
|
||||||
String homeTitle = applicationProperties.getUi().getAppName();
|
|
||||||
return (homeTitle != null) ? homeTitle : "Stirling PDF";
|
@Bean(name = "appName")
|
||||||
}
|
public String appName() {
|
||||||
|
String homeTitle = applicationProperties.getUi().getAppName();
|
||||||
@Bean(name = "appVersion")
|
return (homeTitle != null) ? homeTitle : "Stirling PDF";
|
||||||
public String appVersion() {
|
}
|
||||||
String version = getClass().getPackage().getImplementationVersion();
|
|
||||||
return (version != null) ? version : "0.0.0";
|
@Bean(name = "appVersion")
|
||||||
}
|
public String appVersion() {
|
||||||
|
String version = getClass().getPackage().getImplementationVersion();
|
||||||
@Bean(name = "homeText")
|
return (version != null) ? version : "0.0.0";
|
||||||
public String homeText() {
|
}
|
||||||
return (applicationProperties.getUi().getHomeDescription() != null)
|
|
||||||
? applicationProperties.getUi().getHomeDescription()
|
@Bean(name = "homeText")
|
||||||
: "null";
|
public String homeText() {
|
||||||
}
|
return (applicationProperties.getUi().getHomeDescription() != null)
|
||||||
|
? applicationProperties.getUi().getHomeDescription()
|
||||||
@Bean(name = "navBarText")
|
: "null";
|
||||||
public String navBarText() {
|
}
|
||||||
String defaultNavBar =
|
|
||||||
applicationProperties.getUi().getAppNameNavbar() != null
|
@Bean(name = "navBarText")
|
||||||
? applicationProperties.getUi().getAppNameNavbar()
|
public String navBarText() {
|
||||||
: applicationProperties.getUi().getAppName();
|
String defaultNavBar =
|
||||||
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
|
applicationProperties.getUi().getAppNameNavbar() != null
|
||||||
}
|
? applicationProperties.getUi().getAppNameNavbar()
|
||||||
|
: applicationProperties.getUi().getAppName();
|
||||||
@Bean(name = "enableAlphaFunctionality")
|
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
|
||||||
public boolean enableAlphaFunctionality() {
|
}
|
||||||
return applicationProperties.getSystem().getEnableAlphaFunctionality() != null
|
|
||||||
? applicationProperties.getSystem().getEnableAlphaFunctionality()
|
@Bean(name = "enableAlphaFunctionality")
|
||||||
: false;
|
public boolean enableAlphaFunctionality() {
|
||||||
}
|
return applicationProperties.getSystem().getEnableAlphaFunctionality() != null
|
||||||
|
? applicationProperties.getSystem().getEnableAlphaFunctionality()
|
||||||
@Bean(name = "rateLimit")
|
: false;
|
||||||
public boolean rateLimit() {
|
}
|
||||||
String appName = System.getProperty("rateLimit");
|
|
||||||
if (appName == null) appName = System.getenv("rateLimit");
|
@Bean(name = "rateLimit")
|
||||||
return (appName != null) ? Boolean.valueOf(appName) : false;
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ public class SplitPdfBySectionsController {
|
|||||||
float translateX = -subPageWidth * i;
|
float translateX = -subPageWidth * i;
|
||||||
float translateY = height - subPageHeight * (verticalDivisions - j);
|
float translateY = height - subPageHeight * (verticalDivisions - j);
|
||||||
|
|
||||||
|
// Code for google Docs pdfs..
|
||||||
|
// float translateY = -subPageHeight * (verticalDivisions - 1 - j);
|
||||||
|
|
||||||
contentStream.saveGraphicsState();
|
contentStream.saveGraphicsState();
|
||||||
contentStream.addRect(0, 0, subPageWidth, subPageHeight);
|
contentStream.addRect(0, 0, subPageWidth, subPageHeight);
|
||||||
contentStream.clip();
|
contentStream.clip();
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -19,6 +21,10 @@ 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",
|
||||||
@@ -37,7 +43,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(
|
||||||
|
fileInput.getBytes(), originalFilename, htmlFormatsInstalled);
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package stirling.software.SPDF.controller.api.converters;
|
|||||||
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.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,6 +24,10 @@ 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",
|
||||||
@@ -46,7 +52,9 @@ public class ConvertMarkdownToPdf {
|
|||||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||||
String htmlContent = renderer.render(document);
|
String htmlContent = renderer.render(document);
|
||||||
|
|
||||||
byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html");
|
byte[] pdfBytes =
|
||||||
|
FileToPdf.convertHtmlToPdf(
|
||||||
|
htmlContent.getBytes(), "converted.html", htmlFormatsInstalled);
|
||||||
|
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
originalFilename.replaceFirst("[.][^.]+$", "")
|
originalFilename.replaceFirst("[.][^.]+$", "")
|
||||||
|
|||||||
@@ -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,210 +1,210 @@
|
|||||||
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.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.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;
|
||||||
|
|
||||||
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.PDFComparisonAndCount;
|
import stirling.software.SPDF.model.api.PDFComparisonAndCount;
|
||||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
import stirling.software.SPDF.model.api.filter.ContainsTextRequest;
|
import stirling.software.SPDF.model.api.filter.ContainsTextRequest;
|
||||||
import stirling.software.SPDF.model.api.filter.FileSizeRequest;
|
import stirling.software.SPDF.model.api.filter.FileSizeRequest;
|
||||||
import stirling.software.SPDF.model.api.filter.PageRotationRequest;
|
import stirling.software.SPDF.model.api.filter.PageRotationRequest;
|
||||||
import stirling.software.SPDF.model.api.filter.PageSizeRequest;
|
import stirling.software.SPDF.model.api.filter.PageSizeRequest;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/filter")
|
@RequestMapping("/api/v1/filter")
|
||||||
@Tag(name = "Filter", description = "Filter APIs")
|
@Tag(name = "Filter", description = "Filter APIs")
|
||||||
public class FilterController {
|
public class FilterController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF contains set text, returns true if does",
|
summary = "Checks if a PDF contains set text, returns true if does",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request)
|
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String text = request.getText();
|
String text = request.getText();
|
||||||
String pageNumber = request.getPageNumbers();
|
String pageNumber = request.getPageNumbers();
|
||||||
|
|
||||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
||||||
if (PdfUtils.hasText(pdfDocument, pageNumber, text))
|
if (PdfUtils.hasText(pdfDocument, pageNumber, text))
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
pdfDocument, inputFile.getOriginalFilename());
|
pdfDocument, inputFile.getOriginalFilename());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF contains an image",
|
summary = "Checks if a PDF contains an image",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request)
|
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String pageNumber = request.getPageNumbers();
|
String pageNumber = request.getPageNumbers();
|
||||||
|
|
||||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
||||||
if (PdfUtils.hasImages(pdfDocument, pageNumber))
|
if (PdfUtils.hasImages(pdfDocument, pageNumber))
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
pdfDocument, inputFile.getOriginalFilename());
|
pdfDocument, inputFile.getOriginalFilename());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF is greater, less or equal to a setPageCount",
|
summary = "Checks if a PDF is greater, less or equal to a setPageCount",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request)
|
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String pageCount = request.getPageCount();
|
String pageCount = request.getPageCount();
|
||||||
String comparator = request.getComparator();
|
String comparator = request.getComparator();
|
||||||
// Load the PDF
|
// Load the PDF
|
||||||
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
||||||
int actualPageCount = document.getNumberOfPages();
|
int actualPageCount = document.getNumberOfPages();
|
||||||
|
|
||||||
boolean valid = false;
|
boolean valid = false;
|
||||||
// Perform the comparison
|
// Perform the comparison
|
||||||
switch (comparator) {
|
switch (comparator) {
|
||||||
case "Greater":
|
case "Greater":
|
||||||
valid = actualPageCount > Integer.parseInt(pageCount);
|
valid = actualPageCount > Integer.parseInt(pageCount);
|
||||||
break;
|
break;
|
||||||
case "Equal":
|
case "Equal":
|
||||||
valid = actualPageCount == Integer.parseInt(pageCount);
|
valid = actualPageCount == Integer.parseInt(pageCount);
|
||||||
break;
|
break;
|
||||||
case "Less":
|
case "Less":
|
||||||
valid = actualPageCount < Integer.parseInt(pageCount);
|
valid = actualPageCount < Integer.parseInt(pageCount);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF is of a certain size",
|
summary = "Checks if a PDF is of a certain size",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request)
|
public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String standardPageSize = request.getStandardPageSize();
|
String standardPageSize = request.getStandardPageSize();
|
||||||
String comparator = request.getComparator();
|
String comparator = request.getComparator();
|
||||||
|
|
||||||
// Load the PDF
|
// Load the PDF
|
||||||
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
||||||
|
|
||||||
PDPage firstPage = document.getPage(0);
|
PDPage firstPage = document.getPage(0);
|
||||||
PDRectangle actualPageSize = firstPage.getMediaBox();
|
PDRectangle actualPageSize = firstPage.getMediaBox();
|
||||||
|
|
||||||
// Calculate the area of the actual page size
|
// Calculate the area of the actual page size
|
||||||
float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
|
float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();
|
||||||
|
|
||||||
// Get the standard size and calculate its area
|
// Get the standard size and calculate its area
|
||||||
PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
|
PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);
|
||||||
float standardArea = standardSize.getWidth() * standardSize.getHeight();
|
float standardArea = standardSize.getWidth() * standardSize.getHeight();
|
||||||
|
|
||||||
boolean valid = false;
|
boolean valid = false;
|
||||||
// Perform the comparison
|
// Perform the comparison
|
||||||
switch (comparator) {
|
switch (comparator) {
|
||||||
case "Greater":
|
case "Greater":
|
||||||
valid = actualArea > standardArea;
|
valid = actualArea > standardArea;
|
||||||
break;
|
break;
|
||||||
case "Equal":
|
case "Equal":
|
||||||
valid = actualArea == standardArea;
|
valid = actualArea == standardArea;
|
||||||
break;
|
break;
|
||||||
case "Less":
|
case "Less":
|
||||||
valid = actualArea < standardArea;
|
valid = actualArea < standardArea;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF is a set file size",
|
summary = "Checks if a PDF is a set file size",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request)
|
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
String fileSize = request.getFileSize();
|
String fileSize = request.getFileSize();
|
||||||
String comparator = request.getComparator();
|
String comparator = request.getComparator();
|
||||||
|
|
||||||
// Get the file size
|
// Get the file size
|
||||||
long actualFileSize = inputFile.getSize();
|
long actualFileSize = inputFile.getSize();
|
||||||
|
|
||||||
boolean valid = false;
|
boolean valid = false;
|
||||||
// Perform the comparison
|
// Perform the comparison
|
||||||
switch (comparator) {
|
switch (comparator) {
|
||||||
case "Greater":
|
case "Greater":
|
||||||
valid = actualFileSize > Long.parseLong(fileSize);
|
valid = actualFileSize > Long.parseLong(fileSize);
|
||||||
break;
|
break;
|
||||||
case "Equal":
|
case "Equal":
|
||||||
valid = actualFileSize == Long.parseLong(fileSize);
|
valid = actualFileSize == Long.parseLong(fileSize);
|
||||||
break;
|
break;
|
||||||
case "Less":
|
case "Less":
|
||||||
valid = actualFileSize < Long.parseLong(fileSize);
|
valid = actualFileSize < Long.parseLong(fileSize);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF is of a certain rotation",
|
summary = "Checks if a PDF is of a certain rotation",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request)
|
public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
int rotation = request.getRotation();
|
int rotation = request.getRotation();
|
||||||
String comparator = request.getComparator();
|
String comparator = request.getComparator();
|
||||||
|
|
||||||
// Load the PDF
|
// Load the PDF
|
||||||
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
||||||
|
|
||||||
// Get the rotation of the first page
|
// Get the rotation of the first page
|
||||||
PDPage firstPage = document.getPage(0);
|
PDPage firstPage = document.getPage(0);
|
||||||
int actualRotation = firstPage.getRotation();
|
int actualRotation = firstPage.getRotation();
|
||||||
boolean valid = false;
|
boolean valid = false;
|
||||||
// Perform the comparison
|
// Perform the comparison
|
||||||
switch (comparator) {
|
switch (comparator) {
|
||||||
case "Greater":
|
case "Greater":
|
||||||
valid = actualRotation > rotation;
|
valid = actualRotation > rotation;
|
||||||
break;
|
break;
|
||||||
case "Equal":
|
case "Equal":
|
||||||
valid = actualRotation == rotation;
|
valid = actualRotation == rotation;
|
||||||
break;
|
break;
|
||||||
case "Less":
|
case "Less":
|
||||||
valid = actualRotation < rotation;
|
valid = actualRotation < rotation;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
throw new IllegalArgumentException("Invalid comparator: " + comparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class AutoSplitPdfController {
|
public class AutoSplitPdfController {
|
||||||
|
|
||||||
private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
|
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
|
||||||
|
|
||||||
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
|||||||
@@ -1,150 +1,150 @@
|
|||||||
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.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.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
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;
|
||||||
|
|
||||||
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.misc.AddPageNumbersRequest;
|
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class PageNumbersController {
|
public class PageNumbersController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
||||||
|
|
||||||
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add page numbers to a PDF document",
|
summary = "Add page numbers to a PDF document",
|
||||||
description =
|
description =
|
||||||
"This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
|
"This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
|
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
String customMargin = request.getCustomMargin();
|
String customMargin = request.getCustomMargin();
|
||||||
int position = request.getPosition();
|
int position = request.getPosition();
|
||||||
int startingNumber = request.getStartingNumber();
|
int startingNumber = request.getStartingNumber();
|
||||||
String pagesToNumber = request.getPagesToNumber();
|
String pagesToNumber = request.getPagesToNumber();
|
||||||
String customText = request.getCustomText();
|
String customText = request.getCustomText();
|
||||||
int pageNumber = startingNumber;
|
int pageNumber = startingNumber;
|
||||||
byte[] fileBytes = file.getBytes();
|
byte[] fileBytes = file.getBytes();
|
||||||
PDDocument document = PDDocument.load(fileBytes);
|
PDDocument document = PDDocument.load(fileBytes);
|
||||||
|
|
||||||
float marginFactor;
|
float marginFactor;
|
||||||
switch (customMargin.toLowerCase()) {
|
switch (customMargin.toLowerCase()) {
|
||||||
case "small":
|
case "small":
|
||||||
marginFactor = 0.02f;
|
marginFactor = 0.02f;
|
||||||
break;
|
break;
|
||||||
case "medium":
|
case "medium":
|
||||||
marginFactor = 0.035f;
|
marginFactor = 0.035f;
|
||||||
break;
|
break;
|
||||||
case "large":
|
case "large":
|
||||||
marginFactor = 0.05f;
|
marginFactor = 0.05f;
|
||||||
break;
|
break;
|
||||||
case "x-large":
|
case "x-large":
|
||||||
marginFactor = 0.075f;
|
marginFactor = 0.075f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
marginFactor = 0.035f;
|
marginFactor = 0.035f;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
float fontSize = 12.0f;
|
float fontSize = 12.0f;
|
||||||
PDType1Font font = PDType1Font.HELVETICA;
|
PDType1Font font = PDType1Font.HELVETICA;
|
||||||
if (pagesToNumber == null || pagesToNumber.length() == 0) {
|
if (pagesToNumber == null || pagesToNumber.length() == 0) {
|
||||||
pagesToNumber = "all";
|
pagesToNumber = "all";
|
||||||
}
|
}
|
||||||
if (customText == null || customText.length() == 0) {
|
if (customText == null || customText.length() == 0) {
|
||||||
customText = "{n}";
|
customText = "{n}";
|
||||||
}
|
}
|
||||||
List<Integer> pagesToNumberList =
|
List<Integer> pagesToNumberList =
|
||||||
GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages());
|
GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages());
|
||||||
|
|
||||||
for (int i : pagesToNumberList) {
|
for (int i : pagesToNumberList) {
|
||||||
PDPage page = document.getPage(i);
|
PDPage page = document.getPage(i);
|
||||||
PDRectangle pageSize = page.getMediaBox();
|
PDRectangle pageSize = page.getMediaBox();
|
||||||
|
|
||||||
String text =
|
String text =
|
||||||
customText != null
|
customText != null
|
||||||
? customText
|
? customText
|
||||||
.replace("{n}", String.valueOf(pageNumber))
|
.replace("{n}", String.valueOf(pageNumber))
|
||||||
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
||||||
.replace(
|
.replace(
|
||||||
"{filename}",
|
"{filename}",
|
||||||
file.getOriginalFilename()
|
file.getOriginalFilename()
|
||||||
.replaceFirst("[.][^.]+$", ""))
|
.replaceFirst("[.][^.]+$", ""))
|
||||||
: String.valueOf(pageNumber);
|
: String.valueOf(pageNumber);
|
||||||
|
|
||||||
float x, y;
|
float x, y;
|
||||||
|
|
||||||
int xGroup = (position - 1) % 3;
|
int xGroup = (position - 1) % 3;
|
||||||
int yGroup = 2 - (position - 1) / 3;
|
int yGroup = 2 - (position - 1) / 3;
|
||||||
|
|
||||||
switch (xGroup) {
|
switch (xGroup) {
|
||||||
case 0: // left
|
case 0: // left
|
||||||
x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
|
x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
|
||||||
break;
|
break;
|
||||||
case 1: // center
|
case 1: // center
|
||||||
x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
|
x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
|
||||||
break;
|
break;
|
||||||
default: // right
|
default: // right
|
||||||
x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();
|
x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (yGroup) {
|
switch (yGroup) {
|
||||||
case 0: // bottom
|
case 0: // bottom
|
||||||
y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
|
y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
|
||||||
break;
|
break;
|
||||||
case 1: // middle
|
case 1: // middle
|
||||||
y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
|
y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
|
||||||
break;
|
break;
|
||||||
default: // top
|
default: // top
|
||||||
y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();
|
y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
PDPageContentStream contentStream =
|
PDPageContentStream contentStream =
|
||||||
new PDPageContentStream(
|
new PDPageContentStream(
|
||||||
document, page, PDPageContentStream.AppendMode.APPEND, true);
|
document, page, PDPageContentStream.AppendMode.APPEND, true);
|
||||||
contentStream.beginText();
|
contentStream.beginText();
|
||||||
contentStream.setFont(font, fontSize);
|
contentStream.setFont(font, fontSize);
|
||||||
contentStream.newLineAtOffset(x, y);
|
contentStream.newLineAtOffset(x, y);
|
||||||
contentStream.showText(text);
|
contentStream.showText(text);
|
||||||
contentStream.endText();
|
contentStream.endText();
|
||||||
contentStream.close();
|
contentStream.close();
|
||||||
|
|
||||||
pageNumber++;
|
pageNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
document.save(baos);
|
document.save(baos);
|
||||||
document.close();
|
document.close();
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
baos.toByteArray(),
|
baos.toByteArray(),
|
||||||
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf",
|
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf",
|
||||||
MediaType.APPLICATION_PDF);
|
MediaType.APPLICATION_PDF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,6 +5,8 @@ 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;
|
||||||
@@ -132,8 +134,6 @@ public class PipelineProcessor {
|
|||||||
+ operation);
|
+ operation);
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
outputFiles = newOutputFiles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -185,10 +185,12 @@ public class PipelineProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 +198,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);
|
||||||
@@ -216,11 +219,12 @@ public class PipelineProcessor {
|
|||||||
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 = fileName;
|
||||||
@@ -244,6 +248,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,34 @@ 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.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,6 +58,17 @@ 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",
|
||||||
@@ -80,6 +81,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 +89,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 = PDDocument.load(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,171 +1,171 @@
|
|||||||
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.cos.COSDictionary;
|
||||||
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.PDDocumentCatalog;
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
import org.apache.pdfbox.pdmodel.PDResources;
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.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;
|
||||||
|
|
||||||
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.security.SanitizePdfRequest;
|
import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/security")
|
@RequestMapping("/api/v1/security")
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class SanitizeController {
|
public class SanitizeController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Sanitize a PDF file",
|
summary = "Sanitize a PDF file",
|
||||||
description =
|
description =
|
||||||
"This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
"This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
|
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
boolean removeJavaScript = request.isRemoveJavaScript();
|
boolean removeJavaScript = request.isRemoveJavaScript();
|
||||||
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();
|
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();
|
||||||
boolean removeMetadata = request.isRemoveMetadata();
|
boolean removeMetadata = request.isRemoveMetadata();
|
||||||
boolean removeLinks = request.isRemoveLinks();
|
boolean removeLinks = request.isRemoveLinks();
|
||||||
boolean removeFonts = request.isRemoveFonts();
|
boolean removeFonts = request.isRemoveFonts();
|
||||||
|
|
||||||
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
||||||
if (removeJavaScript) {
|
if (removeJavaScript) {
|
||||||
sanitizeJavaScript(document);
|
sanitizeJavaScript(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removeEmbeddedFiles) {
|
if (removeEmbeddedFiles) {
|
||||||
sanitizeEmbeddedFiles(document);
|
sanitizeEmbeddedFiles(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removeMetadata) {
|
if (removeMetadata) {
|
||||||
sanitizeMetadata(document);
|
sanitizeMetadata(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removeLinks) {
|
if (removeLinks) {
|
||||||
sanitizeLinks(document);
|
sanitizeLinks(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removeFonts) {
|
if (removeFonts) {
|
||||||
sanitizeFonts(document);
|
sanitizeFonts(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
document,
|
document,
|
||||||
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
|
||||||
+ "_sanitized.pdf");
|
+ "_sanitized.pdf");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sanitizeJavaScript(PDDocument document) throws IOException {
|
private void sanitizeJavaScript(PDDocument document) throws IOException {
|
||||||
// Get the root dictionary (catalog) of the PDF
|
// Get the root dictionary (catalog) of the PDF
|
||||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||||
|
|
||||||
// Get the Names dictionary
|
// Get the Names dictionary
|
||||||
COSDictionary namesDict =
|
COSDictionary namesDict =
|
||||||
(COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES);
|
(COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES);
|
||||||
|
|
||||||
if (namesDict != null) {
|
if (namesDict != null) {
|
||||||
// Get the JavaScript dictionary
|
// Get the JavaScript dictionary
|
||||||
COSDictionary javaScriptDict =
|
COSDictionary javaScriptDict =
|
||||||
(COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript"));
|
(COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName("JavaScript"));
|
||||||
|
|
||||||
if (javaScriptDict != null) {
|
if (javaScriptDict != null) {
|
||||||
// Remove the JavaScript dictionary
|
// Remove the JavaScript dictionary
|
||||||
namesDict.removeItem(COSName.getPDFName("JavaScript"));
|
namesDict.removeItem(COSName.getPDFName("JavaScript"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (PDPage page : document.getPages()) {
|
for (PDPage page : document.getPages()) {
|
||||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
if (annotation instanceof PDAnnotationWidget) {
|
if (annotation instanceof PDAnnotationWidget) {
|
||||||
PDAnnotationWidget widget = (PDAnnotationWidget) annotation;
|
PDAnnotationWidget widget = (PDAnnotationWidget) annotation;
|
||||||
PDAction action = widget.getAction();
|
PDAction action = widget.getAction();
|
||||||
if (action instanceof PDActionJavaScript) {
|
if (action instanceof PDActionJavaScript) {
|
||||||
widget.setAction(null);
|
widget.setAction(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
||||||
if (acroForm != null) {
|
if (acroForm != null) {
|
||||||
for (PDField field : acroForm.getFields()) {
|
for (PDField field : acroForm.getFields()) {
|
||||||
PDFormFieldAdditionalActions actions = field.getActions();
|
PDFormFieldAdditionalActions actions = field.getActions();
|
||||||
if (actions != null) {
|
if (actions != null) {
|
||||||
if (actions.getC() instanceof PDActionJavaScript) {
|
if (actions.getC() instanceof PDActionJavaScript) {
|
||||||
actions.setC(null);
|
actions.setC(null);
|
||||||
}
|
}
|
||||||
if (actions.getF() instanceof PDActionJavaScript) {
|
if (actions.getF() instanceof PDActionJavaScript) {
|
||||||
actions.setF(null);
|
actions.setF(null);
|
||||||
}
|
}
|
||||||
if (actions.getK() instanceof PDActionJavaScript) {
|
if (actions.getK() instanceof PDActionJavaScript) {
|
||||||
actions.setK(null);
|
actions.setK(null);
|
||||||
}
|
}
|
||||||
if (actions.getV() instanceof PDActionJavaScript) {
|
if (actions.getV() instanceof PDActionJavaScript) {
|
||||||
actions.setV(null);
|
actions.setV(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sanitizeEmbeddedFiles(PDDocument document) {
|
private void sanitizeEmbeddedFiles(PDDocument document) {
|
||||||
PDPageTree allPages = document.getPages();
|
PDPageTree allPages = document.getPages();
|
||||||
|
|
||||||
for (PDPage page : allPages) {
|
for (PDPage page : allPages) {
|
||||||
PDResources res = page.getResources();
|
PDResources res = page.getResources();
|
||||||
|
|
||||||
// Remove embedded files from the PDF
|
// Remove embedded files from the PDF
|
||||||
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
|
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sanitizeMetadata(PDDocument document) {
|
private void sanitizeMetadata(PDDocument document) {
|
||||||
PDMetadata metadata = document.getDocumentCatalog().getMetadata();
|
PDMetadata metadata = document.getDocumentCatalog().getMetadata();
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
document.getDocumentCatalog().setMetadata(null);
|
document.getDocumentCatalog().setMetadata(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sanitizeLinks(PDDocument document) throws IOException {
|
private void sanitizeLinks(PDDocument document) throws IOException {
|
||||||
for (PDPage page : document.getPages()) {
|
for (PDPage page : document.getPages()) {
|
||||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
if (annotation instanceof PDAnnotationLink) {
|
if (annotation instanceof PDAnnotationLink) {
|
||||||
PDAction action = ((PDAnnotationLink) annotation).getAction();
|
PDAction action = ((PDAnnotationLink) annotation).getAction();
|
||||||
if (action instanceof PDActionLaunch || action instanceof PDActionURI) {
|
if (action instanceof PDActionLaunch || action instanceof PDActionURI) {
|
||||||
((PDAnnotationLink) annotation).setAction(null);
|
((PDAnnotationLink) annotation).setAction(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sanitizeFonts(PDDocument document) {
|
private void sanitizeFonts(PDDocument document) {
|
||||||
for (PDPage page : document.getPages()) {
|
for (PDPage page : document.getPages()) {
|
||||||
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
|
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ public class SignPDFWithCertRequest extends PDFFile {
|
|||||||
|
|
||||||
@Schema(
|
@Schema(
|
||||||
description = "The type of the digital certificate",
|
description = "The type of the digital certificate",
|
||||||
allowableValues = {"PKCS12", "PEM"})
|
allowableValues = {"PEM", "PKCS12", "JKS"})
|
||||||
private String certType;
|
private String certType;
|
||||||
|
|
||||||
@Schema(
|
@Schema(
|
||||||
@@ -28,6 +28,9 @@ public class SignPDFWithCertRequest extends PDFFile {
|
|||||||
@Schema(description = "The PKCS12 keystore file (required for PKCS12 type certificates)")
|
@Schema(description = "The PKCS12 keystore file (required for PKCS12 type certificates)")
|
||||||
private MultipartFile p12File;
|
private MultipartFile p12File;
|
||||||
|
|
||||||
|
@Schema(description = "The JKS keystore file (Java Key Store)")
|
||||||
|
private MultipartFile jksFile;
|
||||||
|
|
||||||
@Schema(description = "The password for the keystore or the private key")
|
@Schema(description = "The password for the keystore or the private key")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import java.util.zip.ZipInputStream;
|
|||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
|
||||||
public class FileToPdf {
|
public class FileToPdf {
|
||||||
public static byte[] convertHtmlToPdf(byte[] fileBytes, String fileName)
|
|
||||||
|
public static byte[] convertHtmlToPdf(
|
||||||
|
byte[] fileBytes, String fileName, boolean htmlFormatsInstalled)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
@@ -29,11 +31,22 @@ public class FileToPdf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add("weasyprint");
|
if (!htmlFormatsInstalled) {
|
||||||
|
command.add("weasyprint");
|
||||||
|
} else {
|
||||||
|
command.add("wkhtmltopdf");
|
||||||
|
command.add("--enable-local-file-access");
|
||||||
|
}
|
||||||
|
|
||||||
command.add(tempInputFile.toString());
|
command.add(tempInputFile.toString());
|
||||||
command.add(tempOutputFile.toString());
|
command.add(tempOutputFile.toString());
|
||||||
ProcessExecutorResult returnCode;
|
ProcessExecutorResult returnCode;
|
||||||
if (fileName.endsWith(".zip")) {
|
if (fileName.endsWith(".zip")) {
|
||||||
|
|
||||||
|
if (htmlFormatsInstalled) {
|
||||||
|
// command.add(1, "--allow");
|
||||||
|
// command.add(2, tempInputFile.getParent().toString());
|
||||||
|
}
|
||||||
returnCode =
|
returnCode =
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)
|
||||||
.runCommandWithOutputHandling(
|
.runCommandWithOutputHandling(
|
||||||
@@ -97,4 +110,38 @@ public class FileToPdf {
|
|||||||
return htmlFiles.get(0);
|
return htmlFiles.get(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] convertBookTypeToPdf(byte[] bytes, String originalFilename)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
if (originalFilename == null || originalFilename.lastIndexOf('.') == -1) {
|
||||||
|
throw new IllegalArgumentException("Invalid original filename.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf('.'));
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
Path tempInputFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create temp file with appropriate extension
|
||||||
|
tempInputFile = Files.createTempFile("input_", fileExtension);
|
||||||
|
Files.write(tempInputFile, bytes);
|
||||||
|
|
||||||
|
command.add("ebook-convert");
|
||||||
|
command.add(tempInputFile.toString());
|
||||||
|
command.add(tempOutputFile.toString());
|
||||||
|
|
||||||
|
ProcessExecutorResult returnCode =
|
||||||
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.CALIBRE)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
|
return Files.readAllBytes(tempOutputFile);
|
||||||
|
} finally {
|
||||||
|
// Clean up temporary files
|
||||||
|
if (tempInputFile != null) {
|
||||||
|
Files.deleteIfExists(tempInputFile);
|
||||||
|
}
|
||||||
|
Files.deleteIfExists(tempOutputFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,186 +1,186 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
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.nio.file.SimpleFileVisitor;
|
import java.nio.file.SimpleFileVisitor;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
public class GeneralUtils {
|
public class GeneralUtils {
|
||||||
|
|
||||||
public static void deleteDirectory(Path path) throws IOException {
|
public static void deleteDirectory(Path path) throws IOException {
|
||||||
Files.walkFileTree(
|
Files.walkFileTree(
|
||||||
path,
|
path,
|
||||||
new SimpleFileVisitor<Path>() {
|
new SimpleFileVisitor<Path>() {
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Files.delete(file);
|
Files.delete(file);
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
|
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Files.delete(dir);
|
Files.delete(dir);
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String convertToFileName(String name) {
|
public static String convertToFileName(String name) {
|
||||||
String safeName = name.replaceAll("[^a-zA-Z0-9]", "_");
|
String safeName = name.replaceAll("[^a-zA-Z0-9]", "_");
|
||||||
if (safeName.length() > 50) {
|
if (safeName.length() > 50) {
|
||||||
safeName = safeName.substring(0, 50);
|
safeName = safeName.substring(0, 50);
|
||||||
}
|
}
|
||||||
return safeName;
|
return safeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValidURL(String urlStr) {
|
public static boolean isValidURL(String urlStr) {
|
||||||
try {
|
try {
|
||||||
new URL(urlStr);
|
new URL(urlStr);
|
||||||
return true;
|
return true;
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File multipartToFile(MultipartFile multipart) throws IOException {
|
public static File multipartToFile(MultipartFile multipart) throws IOException {
|
||||||
Path tempFile = Files.createTempFile("overlay-", ".pdf");
|
Path tempFile = Files.createTempFile("overlay-", ".pdf");
|
||||||
try (InputStream in = multipart.getInputStream();
|
try (InputStream in = multipart.getInputStream();
|
||||||
FileOutputStream out = new FileOutputStream(tempFile.toFile())) {
|
FileOutputStream out = new FileOutputStream(tempFile.toFile())) {
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
while ((bytesRead = in.read(buffer)) != -1) {
|
while ((bytesRead = in.read(buffer)) != -1) {
|
||||||
out.write(buffer, 0, bytesRead);
|
out.write(buffer, 0, bytesRead);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tempFile.toFile();
|
return tempFile.toFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Long convertSizeToBytes(String sizeStr) {
|
public static Long convertSizeToBytes(String sizeStr) {
|
||||||
if (sizeStr == null) {
|
if (sizeStr == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
sizeStr = sizeStr.trim().toUpperCase();
|
sizeStr = sizeStr.trim().toUpperCase();
|
||||||
try {
|
try {
|
||||||
if (sizeStr.endsWith("KB")) {
|
if (sizeStr.endsWith("KB")) {
|
||||||
return (long)
|
return (long)
|
||||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);
|
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);
|
||||||
} else if (sizeStr.endsWith("MB")) {
|
} else if (sizeStr.endsWith("MB")) {
|
||||||
return (long)
|
return (long)
|
||||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
||||||
* 1024
|
* 1024
|
||||||
* 1024);
|
* 1024);
|
||||||
} else if (sizeStr.endsWith("GB")) {
|
} else if (sizeStr.endsWith("GB")) {
|
||||||
return (long)
|
return (long)
|
||||||
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
(Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))
|
||||||
* 1024
|
* 1024
|
||||||
* 1024
|
* 1024
|
||||||
* 1024);
|
* 1024);
|
||||||
} else if (sizeStr.endsWith("B")) {
|
} else if (sizeStr.endsWith("B")) {
|
||||||
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));
|
||||||
} else {
|
} else {
|
||||||
// Assume MB if no unit is specified
|
// Assume MB if no unit is specified
|
||||||
return (long) (Double.parseDouble(sizeStr) * 1024 * 1024);
|
return (long) (Double.parseDouble(sizeStr) * 1024 * 1024);
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
// The numeric part of the input string cannot be parsed, handle this case
|
// The numeric part of the input string cannot be parsed, handle this case
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> parsePageString(String pageOrder, int totalPages) {
|
public static List<Integer> parsePageString(String pageOrder, int totalPages) {
|
||||||
return parsePageList(pageOrder.split(","), totalPages);
|
return parsePageList(pageOrder.split(","), totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) {
|
public static List<Integer> parsePageList(String[] pageOrderArr, int totalPages) {
|
||||||
List<Integer> newPageOrder = new ArrayList<>();
|
List<Integer> newPageOrder = new ArrayList<>();
|
||||||
|
|
||||||
// loop through the page order array
|
// loop through the page order array
|
||||||
for (String element : pageOrderArr) {
|
for (String element : pageOrderArr) {
|
||||||
if (element.equalsIgnoreCase("all")) {
|
if (element.equalsIgnoreCase("all")) {
|
||||||
for (int i = 0; i < totalPages; i++) {
|
for (int i = 0; i < totalPages; i++) {
|
||||||
newPageOrder.add(i);
|
newPageOrder.add(i);
|
||||||
}
|
}
|
||||||
// As all pages are already added, no need to check further
|
// As all pages are already added, no need to check further
|
||||||
break;
|
break;
|
||||||
} else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) {
|
} else if (element.matches("\\d*n\\+?-?\\d*|\\d*\\+?n")) {
|
||||||
// Handle page order as a function
|
// Handle page order as a function
|
||||||
int coefficient = 0;
|
int coefficient = 0;
|
||||||
int constant = 0;
|
int constant = 0;
|
||||||
boolean coefficientExists = false;
|
boolean coefficientExists = false;
|
||||||
boolean constantExists = false;
|
boolean constantExists = false;
|
||||||
|
|
||||||
if (element.contains("n")) {
|
if (element.contains("n")) {
|
||||||
String[] parts = element.split("n");
|
String[] parts = element.split("n");
|
||||||
if (!parts[0].equals("") && parts[0] != null) {
|
if (!parts[0].equals("") && parts[0] != null) {
|
||||||
coefficient = Integer.parseInt(parts[0]);
|
coefficient = Integer.parseInt(parts[0]);
|
||||||
coefficientExists = true;
|
coefficientExists = true;
|
||||||
}
|
}
|
||||||
if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) {
|
if (parts.length > 1 && !parts[1].equals("") && parts[1] != null) {
|
||||||
constant = Integer.parseInt(parts[1]);
|
constant = Integer.parseInt(parts[1]);
|
||||||
constantExists = true;
|
constantExists = true;
|
||||||
}
|
}
|
||||||
} else if (element.contains("+")) {
|
} else if (element.contains("+")) {
|
||||||
constant = Integer.parseInt(element.replace("+", ""));
|
constant = Integer.parseInt(element.replace("+", ""));
|
||||||
constantExists = true;
|
constantExists = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 1; i <= totalPages; i++) {
|
for (int i = 1; i <= totalPages; i++) {
|
||||||
int pageNum = coefficientExists ? coefficient * i : i;
|
int pageNum = coefficientExists ? coefficient * i : i;
|
||||||
pageNum += constantExists ? constant : 0;
|
pageNum += constantExists ? constant : 0;
|
||||||
|
|
||||||
if (pageNum <= totalPages && pageNum > 0) {
|
if (pageNum <= totalPages && pageNum > 0) {
|
||||||
newPageOrder.add(pageNum - 1);
|
newPageOrder.add(pageNum - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (element.contains("-")) {
|
} else if (element.contains("-")) {
|
||||||
// split the range into start and end page
|
// split the range into start and end page
|
||||||
String[] range = element.split("-");
|
String[] range = element.split("-");
|
||||||
int start = Integer.parseInt(range[0]);
|
int start = Integer.parseInt(range[0]);
|
||||||
int end = Integer.parseInt(range[1]);
|
int end = Integer.parseInt(range[1]);
|
||||||
// check if the end page is greater than total pages
|
// check if the end page is greater than total pages
|
||||||
if (end > totalPages) {
|
if (end > totalPages) {
|
||||||
end = totalPages;
|
end = totalPages;
|
||||||
}
|
}
|
||||||
// loop through the range of pages
|
// loop through the range of pages
|
||||||
for (int j = start; j <= end; j++) {
|
for (int j = start; j <= end; j++) {
|
||||||
// print the current index
|
// print the current index
|
||||||
newPageOrder.add(j - 1);
|
newPageOrder.add(j - 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if the element is a single page
|
// if the element is a single page
|
||||||
newPageOrder.add(Integer.parseInt(element) - 1);
|
newPageOrder.add(Integer.parseInt(element) - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newPageOrder;
|
return newPageOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean createDir(String path) {
|
public static boolean createDir(String path) {
|
||||||
Path folder = Paths.get(path);
|
Path folder = Paths.get(path);
|
||||||
if (!Files.exists(folder)) {
|
if (!Files.exists(folder)) {
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(folder);
|
Files.createDirectories(folder);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,400 +1,400 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
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 java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import javax.imageio.IIOImage;
|
import javax.imageio.IIOImage;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageReader;
|
import javax.imageio.ImageReader;
|
||||||
import javax.imageio.ImageWriteParam;
|
import javax.imageio.ImageWriteParam;
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.ImageWriter;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
|
||||||
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.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
|
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
|
||||||
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.rendering.ImageType;
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import stirling.software.SPDF.pdf.ImageFinder;
|
import stirling.software.SPDF.pdf.ImageFinder;
|
||||||
|
|
||||||
public class PdfUtils {
|
public class PdfUtils {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
||||||
|
|
||||||
public static PDRectangle textToPageSize(String size) {
|
public static PDRectangle textToPageSize(String size) {
|
||||||
switch (size.toUpperCase()) {
|
switch (size.toUpperCase()) {
|
||||||
case "A0":
|
case "A0":
|
||||||
return PDRectangle.A0;
|
return PDRectangle.A0;
|
||||||
case "A1":
|
case "A1":
|
||||||
return PDRectangle.A1;
|
return PDRectangle.A1;
|
||||||
case "A2":
|
case "A2":
|
||||||
return PDRectangle.A2;
|
return PDRectangle.A2;
|
||||||
case "A3":
|
case "A3":
|
||||||
return PDRectangle.A3;
|
return PDRectangle.A3;
|
||||||
case "A4":
|
case "A4":
|
||||||
return PDRectangle.A4;
|
return PDRectangle.A4;
|
||||||
case "A5":
|
case "A5":
|
||||||
return PDRectangle.A5;
|
return PDRectangle.A5;
|
||||||
case "A6":
|
case "A6":
|
||||||
return PDRectangle.A6;
|
return PDRectangle.A6;
|
||||||
case "LETTER":
|
case "LETTER":
|
||||||
return PDRectangle.LETTER;
|
return PDRectangle.LETTER;
|
||||||
case "LEGAL":
|
case "LEGAL":
|
||||||
return PDRectangle.LEGAL;
|
return PDRectangle.LEGAL;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Invalid standard page size: " + size);
|
throw new IllegalArgumentException("Invalid standard page size: " + size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException {
|
public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException {
|
||||||
String[] pageOrderArr = pagesToCheck.split(",");
|
String[] pageOrderArr = pagesToCheck.split(",");
|
||||||
List<Integer> pageList =
|
List<Integer> pageList =
|
||||||
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
|
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
|
||||||
|
|
||||||
for (int pageNumber : pageList) {
|
for (int pageNumber : pageList) {
|
||||||
PDPage page = document.getPage(pageNumber);
|
PDPage page = document.getPage(pageNumber);
|
||||||
if (hasImagesOnPage(page)) {
|
if (hasImagesOnPage(page)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase)
|
public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String[] pageOrderArr = pageNumbersToCheck.split(",");
|
String[] pageOrderArr = pageNumbersToCheck.split(",");
|
||||||
List<Integer> pageList =
|
List<Integer> pageList =
|
||||||
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
|
GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());
|
||||||
|
|
||||||
for (int pageNumber : pageList) {
|
for (int pageNumber : pageList) {
|
||||||
PDPage page = document.getPage(pageNumber);
|
PDPage page = document.getPage(pageNumber);
|
||||||
if (hasTextOnPage(page, phrase)) {
|
if (hasTextOnPage(page, phrase)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasImagesOnPage(PDPage page) throws IOException {
|
public static boolean hasImagesOnPage(PDPage page) throws IOException {
|
||||||
ImageFinder imageFinder = new ImageFinder(page);
|
ImageFinder imageFinder = new ImageFinder(page);
|
||||||
imageFinder.processPage(page);
|
imageFinder.processPage(page);
|
||||||
return imageFinder.hasImages();
|
return imageFinder.hasImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException {
|
public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException {
|
||||||
PDFTextStripper textStripper = new PDFTextStripper();
|
PDFTextStripper textStripper = new PDFTextStripper();
|
||||||
PDDocument tempDoc = new PDDocument();
|
PDDocument tempDoc = new PDDocument();
|
||||||
tempDoc.addPage(page);
|
tempDoc.addPage(page);
|
||||||
String pageText = textStripper.getText(tempDoc);
|
String pageText = textStripper.getText(tempDoc);
|
||||||
tempDoc.close();
|
tempDoc.close();
|
||||||
return pageText.contains(phrase);
|
return pageText.contains(phrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck)
|
public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
PDFTextStripper textStripper = new PDFTextStripper();
|
PDFTextStripper textStripper = new PDFTextStripper();
|
||||||
String pdfText = "";
|
String pdfText = "";
|
||||||
|
|
||||||
if (pagesToCheck == null || pagesToCheck.equals("all")) {
|
if (pagesToCheck == null || pagesToCheck.equals("all")) {
|
||||||
pdfText = textStripper.getText(pdfDocument);
|
pdfText = textStripper.getText(pdfDocument);
|
||||||
} else {
|
} else {
|
||||||
// remove whitespaces
|
// remove whitespaces
|
||||||
pagesToCheck = pagesToCheck.replaceAll("\\s+", "");
|
pagesToCheck = pagesToCheck.replaceAll("\\s+", "");
|
||||||
|
|
||||||
String[] splitPoints = pagesToCheck.split(",");
|
String[] splitPoints = pagesToCheck.split(",");
|
||||||
for (String splitPoint : splitPoints) {
|
for (String splitPoint : splitPoints) {
|
||||||
if (splitPoint.contains("-")) {
|
if (splitPoint.contains("-")) {
|
||||||
// Handle page ranges
|
// Handle page ranges
|
||||||
String[] range = splitPoint.split("-");
|
String[] range = splitPoint.split("-");
|
||||||
int startPage = Integer.parseInt(range[0]);
|
int startPage = Integer.parseInt(range[0]);
|
||||||
int endPage = Integer.parseInt(range[1]);
|
int endPage = Integer.parseInt(range[1]);
|
||||||
|
|
||||||
for (int i = startPage; i <= endPage; i++) {
|
for (int i = startPage; i <= endPage; i++) {
|
||||||
textStripper.setStartPage(i);
|
textStripper.setStartPage(i);
|
||||||
textStripper.setEndPage(i);
|
textStripper.setEndPage(i);
|
||||||
pdfText += textStripper.getText(pdfDocument);
|
pdfText += textStripper.getText(pdfDocument);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle individual page
|
// Handle individual page
|
||||||
int page = Integer.parseInt(splitPoint);
|
int page = Integer.parseInt(splitPoint);
|
||||||
textStripper.setStartPage(page);
|
textStripper.setStartPage(page);
|
||||||
textStripper.setEndPage(page);
|
textStripper.setEndPage(page);
|
||||||
pdfText += textStripper.getText(pdfDocument);
|
pdfText += textStripper.getText(pdfDocument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pdfDocument.close();
|
pdfDocument.close();
|
||||||
|
|
||||||
return pdfText.contains(text);
|
return pdfText.contains(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator)
|
public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
int actualPageCount = pdfDocument.getNumberOfPages();
|
int actualPageCount = pdfDocument.getNumberOfPages();
|
||||||
pdfDocument.close();
|
pdfDocument.close();
|
||||||
|
|
||||||
switch (comparator.toLowerCase()) {
|
switch (comparator.toLowerCase()) {
|
||||||
case "greater":
|
case "greater":
|
||||||
return actualPageCount > pageCount;
|
return actualPageCount > pageCount;
|
||||||
case "equal":
|
case "equal":
|
||||||
return actualPageCount == pageCount;
|
return actualPageCount == pageCount;
|
||||||
case "less":
|
case "less":
|
||||||
return actualPageCount < pageCount;
|
return actualPageCount < pageCount;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Invalid comparator. Only 'greater', 'equal', and 'less' are supported.");
|
"Invalid comparator. Only 'greater', 'equal', and 'less' are supported.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException {
|
public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException {
|
||||||
PDPage firstPage = pdfDocument.getPage(0);
|
PDPage firstPage = pdfDocument.getPage(0);
|
||||||
PDRectangle mediaBox = firstPage.getMediaBox();
|
PDRectangle mediaBox = firstPage.getMediaBox();
|
||||||
|
|
||||||
float actualPageWidth = mediaBox.getWidth();
|
float actualPageWidth = mediaBox.getWidth();
|
||||||
float actualPageHeight = mediaBox.getHeight();
|
float actualPageHeight = mediaBox.getHeight();
|
||||||
|
|
||||||
pdfDocument.close();
|
pdfDocument.close();
|
||||||
|
|
||||||
// Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4
|
// Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4
|
||||||
String[] dimensions = expectedPageSize.split("x");
|
String[] dimensions = expectedPageSize.split("x");
|
||||||
float expectedPageWidth = Float.parseFloat(dimensions[0]);
|
float expectedPageWidth = Float.parseFloat(dimensions[0]);
|
||||||
float expectedPageHeight = Float.parseFloat(dimensions[1]);
|
float expectedPageHeight = Float.parseFloat(dimensions[1]);
|
||||||
|
|
||||||
// Checks if the actual page size matches the expected page size
|
// Checks if the actual page size matches the expected page size
|
||||||
return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight;
|
return actualPageWidth == expectedPageWidth && actualPageHeight == expectedPageHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] convertFromPdf(
|
public static byte[] convertFromPdf(
|
||||||
byte[] inputStream,
|
byte[] inputStream,
|
||||||
String imageType,
|
String imageType,
|
||||||
ImageType colorType,
|
ImageType colorType,
|
||||||
boolean singleImage,
|
boolean singleImage,
|
||||||
int DPI,
|
int DPI,
|
||||||
String filename)
|
String filename)
|
||||||
throws IOException, Exception {
|
throws IOException, Exception {
|
||||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
|
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputStream))) {
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
int pageCount = document.getNumberOfPages();
|
int pageCount = document.getNumberOfPages();
|
||||||
|
|
||||||
// Create a ByteArrayOutputStream to save the image(s) to
|
// Create a ByteArrayOutputStream to save the image(s) to
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
if (singleImage) {
|
if (singleImage) {
|
||||||
if (imageType.toLowerCase().equals("tiff")
|
if (imageType.toLowerCase().equals("tiff")
|
||||||
|| imageType.toLowerCase().equals("tif")) {
|
|| imageType.toLowerCase().equals("tif")) {
|
||||||
// Write the images to the output stream as a TIFF with multiple frames
|
// Write the images to the output stream as a TIFF with multiple frames
|
||||||
ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next();
|
ImageWriter writer = ImageIO.getImageWritersByFormatName("tiff").next();
|
||||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||||
param.setCompressionType("ZLib");
|
param.setCompressionType("ZLib");
|
||||||
param.setCompressionQuality(1.0f);
|
param.setCompressionQuality(1.0f);
|
||||||
|
|
||||||
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {
|
try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {
|
||||||
writer.setOutput(ios);
|
writer.setOutput(ios);
|
||||||
writer.prepareWriteSequence(null);
|
writer.prepareWriteSequence(null);
|
||||||
|
|
||||||
for (int i = 0; i < pageCount; ++i) {
|
for (int i = 0; i < pageCount; ++i) {
|
||||||
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
|
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
|
||||||
writer.writeToSequence(new IIOImage(image, null, null), param);
|
writer.writeToSequence(new IIOImage(image, null, null), param);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.endWriteSequence();
|
writer.endWriteSequence();
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.dispose();
|
writer.dispose();
|
||||||
} else {
|
} else {
|
||||||
// Combine all images into a single big image
|
// Combine all images into a single big image
|
||||||
BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType);
|
BufferedImage image = pdfRenderer.renderImageWithDPI(0, DPI, colorType);
|
||||||
BufferedImage combined =
|
BufferedImage combined =
|
||||||
new BufferedImage(
|
new BufferedImage(
|
||||||
image.getWidth(),
|
image.getWidth(),
|
||||||
image.getHeight() * pageCount,
|
image.getHeight() * pageCount,
|
||||||
BufferedImage.TYPE_INT_RGB);
|
BufferedImage.TYPE_INT_RGB);
|
||||||
Graphics g = combined.getGraphics();
|
Graphics g = combined.getGraphics();
|
||||||
|
|
||||||
for (int i = 0; i < pageCount; ++i) {
|
for (int i = 0; i < pageCount; ++i) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
|
image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
|
||||||
}
|
}
|
||||||
g.drawImage(image, 0, i * image.getHeight(), null);
|
g.drawImage(image, 0, i * image.getHeight(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the image to the output stream
|
// Write the image to the output stream
|
||||||
ImageIO.write(combined, imageType, baos);
|
ImageIO.write(combined, imageType, baos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log that the image was successfully written to the byte array
|
// Log that the image was successfully written to the byte array
|
||||||
logger.info("Image successfully written to byte array");
|
logger.info("Image successfully written to byte array");
|
||||||
} else {
|
} else {
|
||||||
// Zip the images and return as byte array
|
// Zip the images and return as byte array
|
||||||
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
|
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
|
||||||
for (int i = 0; i < pageCount; ++i) {
|
for (int i = 0; i < pageCount; ++i) {
|
||||||
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
|
BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);
|
||||||
try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) {
|
try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) {
|
||||||
ImageIO.write(image, imageType, baosImage);
|
ImageIO.write(image, imageType, baosImage);
|
||||||
|
|
||||||
// Add the image to the zip file
|
// Add the image to the zip file
|
||||||
zos.putNextEntry(
|
zos.putNextEntry(
|
||||||
new ZipEntry(
|
new ZipEntry(
|
||||||
String.format(
|
String.format(
|
||||||
filename + "_%d.%s",
|
filename + "_%d.%s",
|
||||||
i + 1,
|
i + 1,
|
||||||
imageType.toLowerCase())));
|
imageType.toLowerCase())));
|
||||||
zos.write(baosImage.toByteArray());
|
zos.write(baosImage.toByteArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Log that the images were successfully written to the byte array
|
// Log that the images were successfully written to the byte array
|
||||||
logger.info("Images successfully written to byte array as a zip");
|
logger.info("Images successfully written to byte array as a zip");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return baos.toByteArray();
|
return baos.toByteArray();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Log an error message if there is an issue converting the PDF to an image
|
// Log an error message if there is an issue converting the PDF to an image
|
||||||
logger.error("Error converting PDF to image", e);
|
logger.error("Error converting PDF to image", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] imageToPdf(
|
public static byte[] imageToPdf(
|
||||||
MultipartFile[] files, String fitOption, boolean autoRotate, String colorType)
|
MultipartFile[] files, String fitOption, boolean autoRotate, String colorType)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
try (PDDocument doc = new PDDocument()) {
|
try (PDDocument doc = new PDDocument()) {
|
||||||
for (MultipartFile file : files) {
|
for (MultipartFile file : files) {
|
||||||
String contentType = file.getContentType();
|
String contentType = file.getContentType();
|
||||||
String originalFilename = file.getOriginalFilename();
|
String originalFilename = file.getOriginalFilename();
|
||||||
if (originalFilename != null
|
if (originalFilename != null
|
||||||
&& (originalFilename.toLowerCase().endsWith(".tiff")
|
&& (originalFilename.toLowerCase().endsWith(".tiff")
|
||||||
|| originalFilename.toLowerCase().endsWith(".tif"))) {
|
|| originalFilename.toLowerCase().endsWith(".tif"))) {
|
||||||
ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next();
|
ImageReader reader = ImageIO.getImageReadersByFormatName("tiff").next();
|
||||||
reader.setInput(ImageIO.createImageInputStream(file.getInputStream()));
|
reader.setInput(ImageIO.createImageInputStream(file.getInputStream()));
|
||||||
int numPages = reader.getNumImages(true);
|
int numPages = reader.getNumImages(true);
|
||||||
for (int i = 0; i < numPages; i++) {
|
for (int i = 0; i < numPages; i++) {
|
||||||
BufferedImage pageImage = reader.read(i);
|
BufferedImage pageImage = reader.read(i);
|
||||||
BufferedImage convertedImage =
|
BufferedImage convertedImage =
|
||||||
ImageProcessingUtils.convertColorType(pageImage, colorType);
|
ImageProcessingUtils.convertColorType(pageImage, colorType);
|
||||||
PDImageXObject pdImage =
|
PDImageXObject pdImage =
|
||||||
LosslessFactory.createFromImage(doc, convertedImage);
|
LosslessFactory.createFromImage(doc, convertedImage);
|
||||||
addImageToDocument(doc, pdImage, fitOption, autoRotate);
|
addImageToDocument(doc, pdImage, fitOption, autoRotate);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
BufferedImage image = ImageIO.read(file.getInputStream());
|
BufferedImage image = ImageIO.read(file.getInputStream());
|
||||||
BufferedImage convertedImage =
|
BufferedImage convertedImage =
|
||||||
ImageProcessingUtils.convertColorType(image, colorType);
|
ImageProcessingUtils.convertColorType(image, colorType);
|
||||||
// Use JPEGFactory if it's JPEG since JPEG is lossy
|
// Use JPEGFactory if it's JPEG since JPEG is lossy
|
||||||
PDImageXObject pdImage =
|
PDImageXObject pdImage =
|
||||||
(contentType != null && contentType.equals("image/jpeg"))
|
(contentType != null && contentType.equals("image/jpeg"))
|
||||||
? JPEGFactory.createFromImage(doc, convertedImage)
|
? JPEGFactory.createFromImage(doc, convertedImage)
|
||||||
: LosslessFactory.createFromImage(doc, convertedImage);
|
: LosslessFactory.createFromImage(doc, convertedImage);
|
||||||
addImageToDocument(doc, pdImage, fitOption, autoRotate);
|
addImageToDocument(doc, pdImage, fitOption, autoRotate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
doc.save(byteArrayOutputStream);
|
doc.save(byteArrayOutputStream);
|
||||||
logger.info("PDF successfully saved to byte array");
|
logger.info("PDF successfully saved to byte array");
|
||||||
return byteArrayOutputStream.toByteArray();
|
return byteArrayOutputStream.toByteArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addImageToDocument(
|
private static void addImageToDocument(
|
||||||
PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate)
|
PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
boolean imageIsLandscape = image.getWidth() > image.getHeight();
|
boolean imageIsLandscape = image.getWidth() > image.getHeight();
|
||||||
PDRectangle pageSize = PDRectangle.A4;
|
PDRectangle pageSize = PDRectangle.A4;
|
||||||
|
|
||||||
System.out.println(fitOption);
|
System.out.println(fitOption);
|
||||||
|
|
||||||
if (autoRotate && imageIsLandscape) {
|
if (autoRotate && imageIsLandscape) {
|
||||||
pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth());
|
pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("fitDocumentToImage".equals(fitOption)) {
|
if ("fitDocumentToImage".equals(fitOption)) {
|
||||||
pageSize = new PDRectangle(image.getWidth(), image.getHeight());
|
pageSize = new PDRectangle(image.getWidth(), image.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
PDPage page = new PDPage(pageSize);
|
PDPage page = new PDPage(pageSize);
|
||||||
doc.addPage(page);
|
doc.addPage(page);
|
||||||
|
|
||||||
float pageWidth = page.getMediaBox().getWidth();
|
float pageWidth = page.getMediaBox().getWidth();
|
||||||
float pageHeight = page.getMediaBox().getHeight();
|
float pageHeight = page.getMediaBox().getHeight();
|
||||||
|
|
||||||
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
|
try (PDPageContentStream contentStream = new PDPageContentStream(doc, page)) {
|
||||||
if ("fillPage".equals(fitOption) || "fitDocumentToImage".equals(fitOption)) {
|
if ("fillPage".equals(fitOption) || "fitDocumentToImage".equals(fitOption)) {
|
||||||
contentStream.drawImage(image, 0, 0, pageWidth, pageHeight);
|
contentStream.drawImage(image, 0, 0, pageWidth, pageHeight);
|
||||||
} else if ("maintainAspectRatio".equals(fitOption)) {
|
} else if ("maintainAspectRatio".equals(fitOption)) {
|
||||||
float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight();
|
float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight();
|
||||||
float pageAspectRatio = pageWidth / pageHeight;
|
float pageAspectRatio = pageWidth / pageHeight;
|
||||||
|
|
||||||
float scaleFactor = 1.0f;
|
float scaleFactor = 1.0f;
|
||||||
if (imageAspectRatio > pageAspectRatio) {
|
if (imageAspectRatio > pageAspectRatio) {
|
||||||
scaleFactor = pageWidth / image.getWidth();
|
scaleFactor = pageWidth / image.getWidth();
|
||||||
} else {
|
} else {
|
||||||
scaleFactor = pageHeight / image.getHeight();
|
scaleFactor = pageHeight / image.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2;
|
float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2;
|
||||||
float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2;
|
float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2;
|
||||||
contentStream.drawImage(
|
contentStream.drawImage(
|
||||||
image,
|
image,
|
||||||
xPos,
|
xPos,
|
||||||
yPos,
|
yPos,
|
||||||
image.getWidth() * scaleFactor,
|
image.getWidth() * scaleFactor,
|
||||||
image.getHeight() * scaleFactor);
|
image.getHeight() * scaleFactor);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error adding image to PDF", e);
|
logger.error("Error adding image to PDF", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] overlayImage(
|
public static byte[] overlayImage(
|
||||||
byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage)
|
byte[] pdfBytes, byte[] imageBytes, float x, float y, boolean everyPage)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes));
|
PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes));
|
||||||
|
|
||||||
// Get the first page of the PDF
|
// Get the first page of the PDF
|
||||||
int pages = document.getNumberOfPages();
|
int pages = document.getNumberOfPages();
|
||||||
for (int i = 0; i < pages; i++) {
|
for (int i = 0; i < pages; i++) {
|
||||||
PDPage page = document.getPage(i);
|
PDPage page = document.getPage(i);
|
||||||
try (PDPageContentStream contentStream =
|
try (PDPageContentStream contentStream =
|
||||||
new PDPageContentStream(
|
new PDPageContentStream(
|
||||||
document, page, PDPageContentStream.AppendMode.APPEND, true)) {
|
document, page, PDPageContentStream.AppendMode.APPEND, true)) {
|
||||||
// Create an image object from the image bytes
|
// Create an image object from the image bytes
|
||||||
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
|
PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, "");
|
||||||
// Draw the image onto the page at the specified x and y coordinates
|
// Draw the image onto the page at the specified x and y coordinates
|
||||||
contentStream.drawImage(image, x, y);
|
contentStream.drawImage(image, x, y);
|
||||||
logger.info("Image successfully overlayed onto PDF");
|
logger.info("Image successfully overlayed onto PDF");
|
||||||
if (!everyPage && i == 0) {
|
if (!everyPage && i == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Log an error message if there is an issue overlaying the image onto the PDF
|
// Log an error message if there is an issue overlaying the image onto the PDF
|
||||||
logger.error("Error overlaying image onto PDF", e);
|
logger.error("Error overlaying image onto PDF", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create a ByteArrayOutputStream to save the PDF to
|
// Create a ByteArrayOutputStream to save the PDF to
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
document.save(baos);
|
document.save(baos);
|
||||||
logger.info("PDF successfully saved to byte array");
|
logger.info("PDF successfully saved to byte array");
|
||||||
return baos.toByteArray();
|
return baos.toByteArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,39 @@ import java.io.BufferedReader;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class ProcessExecutor {
|
public class ProcessExecutor {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ProcessExecutor.class);
|
||||||
|
|
||||||
public enum Processes {
|
public enum Processes {
|
||||||
LIBRE_OFFICE,
|
LIBRE_OFFICE,
|
||||||
OCR_MY_PDF,
|
OCR_MY_PDF,
|
||||||
PYTHON_OPENCV,
|
PYTHON_OPENCV,
|
||||||
GHOSTSCRIPT,
|
GHOSTSCRIPT,
|
||||||
WEASYPRINT
|
WEASYPRINT,
|
||||||
|
INSTALL_APP,
|
||||||
|
CALIBRE
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
|
private static final Map<Processes, ProcessExecutor> instances = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public static ProcessExecutor getInstance(Processes processType) {
|
public static ProcessExecutor getInstance(Processes processType) {
|
||||||
|
return getInstance(processType, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProcessExecutor getInstance(Processes processType, boolean liveUpdates) {
|
||||||
return instances.computeIfAbsent(
|
return instances.computeIfAbsent(
|
||||||
processType,
|
processType,
|
||||||
key -> {
|
key -> {
|
||||||
@@ -34,15 +47,32 @@ public class ProcessExecutor {
|
|||||||
case PYTHON_OPENCV -> 8;
|
case PYTHON_OPENCV -> 8;
|
||||||
case GHOSTSCRIPT -> 16;
|
case GHOSTSCRIPT -> 16;
|
||||||
case WEASYPRINT -> 16;
|
case WEASYPRINT -> 16;
|
||||||
|
case INSTALL_APP -> 1;
|
||||||
|
case CALIBRE -> 1;
|
||||||
};
|
};
|
||||||
return new ProcessExecutor(semaphoreLimit);
|
|
||||||
|
long timeoutMinutes =
|
||||||
|
switch (key) {
|
||||||
|
case LIBRE_OFFICE -> 30;
|
||||||
|
case OCR_MY_PDF -> 30;
|
||||||
|
case PYTHON_OPENCV -> 30;
|
||||||
|
case GHOSTSCRIPT -> 5;
|
||||||
|
case WEASYPRINT -> 30;
|
||||||
|
case INSTALL_APP -> 60;
|
||||||
|
case CALIBRE -> 30;
|
||||||
|
};
|
||||||
|
return new ProcessExecutor(semaphoreLimit, liveUpdates, timeoutMinutes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Semaphore semaphore;
|
private final Semaphore semaphore;
|
||||||
|
private final boolean liveUpdates;
|
||||||
|
private long timeoutDuration;
|
||||||
|
|
||||||
private ProcessExecutor(int semaphoreLimit) {
|
private ProcessExecutor(int semaphoreLimit, boolean liveUpdates, long timeout) {
|
||||||
this.semaphore = new Semaphore(semaphoreLimit);
|
this.semaphore = new Semaphore(semaphoreLimit);
|
||||||
|
this.liveUpdates = liveUpdates;
|
||||||
|
this.timeoutDuration = timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProcessExecutorResult runCommandWithOutputHandling(List<String> command)
|
public ProcessExecutorResult runCommandWithOutputHandling(List<String> command)
|
||||||
@@ -52,12 +82,12 @@ public class ProcessExecutor {
|
|||||||
|
|
||||||
public ProcessExecutorResult runCommandWithOutputHandling(
|
public ProcessExecutorResult runCommandWithOutputHandling(
|
||||||
List<String> command, File workingDirectory) throws IOException, InterruptedException {
|
List<String> command, File workingDirectory) throws IOException, InterruptedException {
|
||||||
int exitCode = 1;
|
|
||||||
String messages = "";
|
String messages = "";
|
||||||
|
int exitCode = 1;
|
||||||
semaphore.acquire();
|
semaphore.acquire();
|
||||||
try {
|
try {
|
||||||
|
|
||||||
System.out.print("Running command: " + String.join(" ", command));
|
logger.info("Running command: " + String.join(" ", command));
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||||
|
|
||||||
// Use the working directory if it's set
|
// Use the working directory if it's set
|
||||||
@@ -81,7 +111,11 @@ public class ProcessExecutor {
|
|||||||
String line;
|
String line;
|
||||||
while ((line = errorReader.readLine()) != null) {
|
while ((line = errorReader.readLine()) != null) {
|
||||||
errorLines.add(line);
|
errorLines.add(line);
|
||||||
|
if (liveUpdates) logger.info(line);
|
||||||
}
|
}
|
||||||
|
} catch (InterruptedIOException e) {
|
||||||
|
logger.warn(
|
||||||
|
"Error reader thread was interrupted due to timeout.");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -98,7 +132,11 @@ public class ProcessExecutor {
|
|||||||
String line;
|
String line;
|
||||||
while ((line = outputReader.readLine()) != null) {
|
while ((line = outputReader.readLine()) != null) {
|
||||||
outputLines.add(line);
|
outputLines.add(line);
|
||||||
|
if (liveUpdates) logger.info(line);
|
||||||
}
|
}
|
||||||
|
} catch (InterruptedIOException e) {
|
||||||
|
logger.warn(
|
||||||
|
"Error reader thread was interrupted due to timeout.");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -108,29 +146,42 @@ public class ProcessExecutor {
|
|||||||
outputReaderThread.start();
|
outputReaderThread.start();
|
||||||
|
|
||||||
// Wait for the conversion process to complete
|
// Wait for the conversion process to complete
|
||||||
exitCode = process.waitFor();
|
boolean finished = process.waitFor(timeoutDuration, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
if (!finished) {
|
||||||
|
// Terminate the process
|
||||||
|
process.destroy();
|
||||||
|
// Interrupt the reader threads
|
||||||
|
errorReaderThread.interrupt();
|
||||||
|
outputReaderThread.interrupt();
|
||||||
|
throw new IOException("Process timeout exceeded.");
|
||||||
|
}
|
||||||
|
exitCode = process.exitValue();
|
||||||
// Wait for the reader threads to finish
|
// Wait for the reader threads to finish
|
||||||
errorReaderThread.join();
|
errorReaderThread.join();
|
||||||
outputReaderThread.join();
|
outputReaderThread.join();
|
||||||
|
|
||||||
if (outputLines.size() > 0) {
|
if (!liveUpdates) {
|
||||||
String outputMessage = String.join("\n", outputLines);
|
if (outputLines.size() > 0) {
|
||||||
messages += outputMessage;
|
String outputMessage = String.join("\n", outputLines);
|
||||||
System.out.println("Command output:\n" + outputMessage);
|
messages += outputMessage;
|
||||||
}
|
logger.info("Command output:\n" + outputMessage);
|
||||||
|
|
||||||
if (errorLines.size() > 0) {
|
|
||||||
String errorMessage = String.join("\n", errorLines);
|
|
||||||
messages += errorMessage;
|
|
||||||
System.out.println("Command error output:\n" + errorMessage);
|
|
||||||
if (exitCode != 0) {
|
|
||||||
throw new IOException(
|
|
||||||
"Command process failed with exit code "
|
|
||||||
+ exitCode
|
|
||||||
+ ". Error message: "
|
|
||||||
+ errorMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errorLines.size() > 0) {
|
||||||
|
String errorMessage = String.join("\n", errorLines);
|
||||||
|
messages += errorMessage;
|
||||||
|
logger.warn("Command error output:\n" + errorMessage);
|
||||||
|
if (exitCode != 0) {
|
||||||
|
throw new IOException(
|
||||||
|
"Command process failed with exit code "
|
||||||
|
+ exitCode
|
||||||
|
+ ". Error message: "
|
||||||
|
+ errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (exitCode != 0) {
|
||||||
|
throw new IOException("Command process failed with exit code " + exitCode);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
semaphore.release();
|
semaphore.release();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public class RequestUriUtils {
|
|||||||
|| requestURI.startsWith("/images/")
|
|| requestURI.startsWith("/images/")
|
||||||
|| requestURI.startsWith("/public/")
|
|| requestURI.startsWith("/public/")
|
||||||
|| requestURI.startsWith("/pdfjs/")
|
|| requestURI.startsWith("/pdfjs/")
|
||||||
|| requestURI.endsWith(".svg");
|
|| requestURI.endsWith(".svg")
|
||||||
|
|| requestURI.startsWith("/api/v1/info/status");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,67 @@
|
|||||||
package stirling.software.SPDF.utils;
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
public class WebResponseUtils {
|
public class WebResponseUtils {
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> boasToWebResponse(
|
public static ResponseEntity<byte[]> boasToWebResponse(
|
||||||
ByteArrayOutputStream baos, String docName) throws IOException {
|
ByteArrayOutputStream baos, String docName) throws IOException {
|
||||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName);
|
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> boasToWebResponse(
|
public static ResponseEntity<byte[]> boasToWebResponse(
|
||||||
ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException {
|
ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException {
|
||||||
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType);
|
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> multiPartFileToWebResponse(MultipartFile file)
|
public static ResponseEntity<byte[]> multiPartFileToWebResponse(MultipartFile file)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String fileName = file.getOriginalFilename();
|
String fileName = file.getOriginalFilename();
|
||||||
MediaType mediaType = MediaType.parseMediaType(file.getContentType());
|
MediaType mediaType = MediaType.parseMediaType(file.getContentType());
|
||||||
|
|
||||||
byte[] bytes = file.getBytes();
|
byte[] bytes = file.getBytes();
|
||||||
|
|
||||||
return bytesToWebResponse(bytes, fileName, mediaType);
|
return bytesToWebResponse(bytes, fileName, mediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> bytesToWebResponse(
|
public static ResponseEntity<byte[]> bytesToWebResponse(
|
||||||
byte[] bytes, String docName, MediaType mediaType) throws IOException {
|
byte[] bytes, String docName, MediaType mediaType) throws IOException {
|
||||||
|
|
||||||
// Return the PDF as a response
|
// Return the PDF as a response
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(mediaType);
|
headers.setContentType(mediaType);
|
||||||
headers.setContentLength(bytes.length);
|
headers.setContentLength(bytes.length);
|
||||||
String encodedDocName =
|
String encodedDocName =
|
||||||
URLEncoder.encode(docName, StandardCharsets.UTF_8.toString())
|
URLEncoder.encode(docName, StandardCharsets.UTF_8.toString())
|
||||||
.replaceAll("\\+", "%20");
|
.replaceAll("\\+", "%20");
|
||||||
headers.setContentDispositionFormData("attachment", encodedDocName);
|
headers.setContentDispositionFormData("attachment", encodedDocName);
|
||||||
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName)
|
public static ResponseEntity<byte[]> bytesToWebResponse(byte[] bytes, String docName)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF);
|
return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName)
|
public static ResponseEntity<byte[]> pdfDocToWebResponse(PDDocument document, String docName)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
// Open Byte Array and save document to it
|
// Open Byte Array and save document to it
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
document.save(baos);
|
document.save(baos);
|
||||||
// Close the document
|
// Close the document
|
||||||
document.close();
|
document.close();
|
||||||
|
|
||||||
return boasToWebResponse(baos, docName);
|
return boasToWebResponse(baos, docName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
fileToPDF.fileTypesList=Microsoft Word: (DOC, DOCX, DOT, DOTX) <br> \
|
fileToPDF.fileTypesList=Microsoft Word: (DOC, DOCX, DOT, DOTX) <br> \
|
||||||
Microsoft Excel: (CSV, XLS, XLSX, XLT, XLTX, SLK, DIF) <br> \
|
Microsoft Excel: (CSV, XLS, XLSX, XLT, XLTX, SLK, DIF) <br> \
|
||||||
Microsoft PowerPoint: (PPT, PPTX) <br> \
|
Microsoft PowerPoint: (PPT, PPTX) <br> \
|
||||||
OpenDocument Formats: (ODT, OTT, ODS, OTS, ODP, OTP, ODG, OTG) <br> \
|
OpenDocument Formats: (ODT, OTT, ODS, OTS, ODP, OTP, ODG, OTG) <br> \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Red
|
|||||||
green=Green
|
green=Green
|
||||||
blue=Blue
|
blue=Blue
|
||||||
custom=Custom...
|
custom=Custom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Credentials changed!
|
changedCredsMessage=Credentials changed!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Current password is incorrect.
|
|||||||
usernameExistsMessage=New Username already exists.
|
usernameExistsMessage=New Username already exists.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Submit
|
|||||||
certSign.title=توقيع الشهادة
|
certSign.title=توقيع الشهادة
|
||||||
certSign.header=قم بتوقيع ملف PDF بشهادتك (العمل قيد التقدم)
|
certSign.header=قم بتوقيع ملف PDF بشهادتك (العمل قيد التقدم)
|
||||||
certSign.selectPDF=حدد ملف PDF للتوقيع:
|
certSign.selectPDF=حدد ملف PDF للتوقيع:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=حدد ملف المفتاح الخاص (تنسيق PKCS # 8 ، يمكن أن يكون .pem أو .der):
|
certSign.selectKey=حدد ملف المفتاح الخاص (تنسيق PKCS # 8 ، يمكن أن يكون .pem أو .der):
|
||||||
certSign.selectCert=حدد ملف الشهادة الخاص بك (تنسيق X.509 ، يمكن أن يكون .pem أو .der):
|
certSign.selectCert=حدد ملف الشهادة الخاص بك (تنسيق X.509 ، يمكن أن يكون .pem أو .der):
|
||||||
certSign.selectP12=حدد ملف تخزين المفاتيح PKCS # 12 (.p12 أو .pfx) (اختياري ، إذا تم توفيره ، يجب أن يحتوي على مفتاحك الخاص وشهادتك):
|
certSign.selectP12=حدد ملف تخزين المفاتيح PKCS # 12 (.p12 أو .pfx) (اختياري ، إذا تم توفيره ، يجب أن يحتوي على مفتاحك الخاص وشهادتك):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=نوع الشهادة
|
certSign.certType=نوع الشهادة
|
||||||
certSign.password=أدخل ملف تخزين المفاتيح أو كلمة المرور الخاصة (إن وجدت):
|
certSign.password=أدخل ملف تخزين المفاتيح أو كلمة المرور الخاصة (إن وجدت):
|
||||||
certSign.showSig=إظهار التوقيع
|
certSign.showSig=إظهار التوقيع
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Червено
|
|||||||
green=Зелено
|
green=Зелено
|
||||||
blue=Синьо
|
blue=Синьо
|
||||||
custom=Персонализиране...
|
custom=Персонализиране...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Идентификационните данни са променени!
|
changedCredsMessage=Идентификационните данни са променени!
|
||||||
notAuthenticatedMessage=Потребителят не е автентикиран.
|
notAuthenticatedMessage=Потребителят не е автентикиран.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Текущата парола е неправилна.
|
|||||||
usernameExistsMessage=Новият потребител вече съществува.
|
usernameExistsMessage=Новият потребител вече съществува.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Подайте
|
|||||||
certSign.title=Подписване на сертификат
|
certSign.title=Подписване на сертификат
|
||||||
certSign.header=Подпишете PDF с вашия сертификат (В процес на работа)
|
certSign.header=Подпишете PDF с вашия сертификат (В процес на работа)
|
||||||
certSign.selectPDF=Изберете PDF файл за подписване:
|
certSign.selectPDF=Изберете PDF файл за подписване:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Изберете вашия файл с личен ключ (формат PKCS#8, може да бъде .pem или .der):
|
certSign.selectKey=Изберете вашия файл с личен ключ (формат PKCS#8, може да бъде .pem или .der):
|
||||||
certSign.selectCert=Изберете вашия файл със сертификат (формат X.509, може да бъде .pem или .der):
|
certSign.selectCert=Изберете вашия файл със сертификат (формат X.509, може да бъде .pem или .der):
|
||||||
certSign.selectP12=Изберете вашия PKCS#12 Keystore файл (.p12 или .pfx) (По избор, ако е предоставен, трябва да съдържа вашия личен ключ и сертификат):
|
certSign.selectP12=Изберете вашия PKCS#12 Keystore файл (.p12 или .pfx) (По избор, ако е предоставен, трябва да съдържа вашия личен ключ и сертификат):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Тип сертификат
|
certSign.certType=Тип сертификат
|
||||||
certSign.password=Въведете вашата парола за Keystore за ключове или частен ключ (ако има):
|
certSign.password=Въведете вашата парола за Keystore за ключове или частен ключ (ако има):
|
||||||
certSign.showSig=Показване на подпис
|
certSign.showSig=Показване на подпис
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Vermell
|
|||||||
green=Verd
|
green=Verd
|
||||||
blue=Blau
|
blue=Blau
|
||||||
custom=Personalitzat...
|
custom=Personalitzat...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Credentials changed!
|
changedCredsMessage=Credentials changed!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Current password is incorrect.
|
|||||||
usernameExistsMessage=New Username already exists.
|
usernameExistsMessage=New Username already exists.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Submit
|
|||||||
certSign.title=Significació del certificat
|
certSign.title=Significació del certificat
|
||||||
certSign.header=Firmar un PDF amb el vostre certificat (Treball en curs)
|
certSign.header=Firmar un PDF amb el vostre certificat (Treball en curs)
|
||||||
certSign.selectPDF=Seleccioneu un fitxer PDF per signar:
|
certSign.selectPDF=Seleccioneu un fitxer PDF per signar:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Seleccioneu el vostre fitxer de clau privada (format PKCS#8, podria ser .pem o .der):
|
certSign.selectKey=Seleccioneu el vostre fitxer de clau privada (format PKCS#8, podria ser .pem o .der):
|
||||||
certSign.selectCert=Seleccioneu el vostre fitxer de certificat (format X.509, podria ser .pem o .der):
|
certSign.selectCert=Seleccioneu el vostre fitxer de certificat (format X.509, podria ser .pem o .der):
|
||||||
certSign.selectP12=Seleccioneu el vostre fitxer de magatzem de claus PKCS#12 (.p12 o .pfx) (Opcional, si es proporciona, hauria de contenir la vostra clau privada i certificat):
|
certSign.selectP12=Seleccioneu el vostre fitxer de magatzem de claus PKCS#12 (.p12 o .pfx) (Opcional, si es proporciona, hauria de contenir la vostra clau privada i certificat):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Tipus de certificat
|
certSign.certType=Tipus de certificat
|
||||||
certSign.password=Introduïu el vostre magatzem de claus o contrasenya de clau privada (si n'hi ha):
|
certSign.password=Introduïu el vostre magatzem de claus o contrasenya de clau privada (si n'hi ha):
|
||||||
certSign.showSig=Mostra la signatura
|
certSign.showSig=Mostra la signatura
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Rot
|
|||||||
green=Grün
|
green=Grün
|
||||||
blue=Blau
|
blue=Blau
|
||||||
custom=benutzerdefiniert...
|
custom=benutzerdefiniert...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Anmeldedaten geändert!
|
changedCredsMessage=Anmeldedaten geändert!
|
||||||
notAuthenticatedMessage=Benutzer nicht authentifiziert.
|
notAuthenticatedMessage=Benutzer nicht authentifiziert.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Das Passwort ist falsch.
|
|||||||
usernameExistsMessage=Neuer Benutzername existiert bereits.
|
usernameExistsMessage=Neuer Benutzername existiert bereits.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Abschicken
|
|||||||
certSign.title=Zertifikatsignierung
|
certSign.title=Zertifikatsignierung
|
||||||
certSign.header=Signieren Sie ein PDF mit Ihrem Zertifikat (in Arbeit)
|
certSign.header=Signieren Sie ein PDF mit Ihrem Zertifikat (in Arbeit)
|
||||||
certSign.selectPDF=Wählen Sie eine PDF-Datei zum Signieren aus:
|
certSign.selectPDF=Wählen Sie eine PDF-Datei zum Signieren aus:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Wählen Sie Ihre private Schlüsseldatei aus (PKCS#8-Format, könnte .pem oder .der sein):
|
certSign.selectKey=Wählen Sie Ihre private Schlüsseldatei aus (PKCS#8-Format, könnte .pem oder .der sein):
|
||||||
certSign.selectCert=Wählen Sie Ihre Zertifikatsdatei aus (X.509-Format, könnte .pem oder .der sein):
|
certSign.selectCert=Wählen Sie Ihre Zertifikatsdatei aus (X.509-Format, könnte .pem oder .der sein):
|
||||||
certSign.selectP12=Wählen Sie Ihre PKCS#12-Keystore-Datei (.p12 oder .pfx) aus (optional, falls angegeben, sollte sie Ihren privaten Schlüssel und Ihr Zertifikat enthalten):
|
certSign.selectP12=Wählen Sie Ihre PKCS#12-Keystore-Datei (.p12 oder .pfx) aus (optional, falls angegeben, sollte sie Ihren privaten Schlüssel und Ihr Zertifikat enthalten):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Zertifikattyp
|
certSign.certType=Zertifikattyp
|
||||||
certSign.password=Geben Sie Ihr Keystore- oder Private-Key-Passwort ein (falls vorhanden):
|
certSign.password=Geben Sie Ihr Keystore- oder Private-Key-Passwort ein (falls vorhanden):
|
||||||
certSign.showSig=Signatur anzeigen
|
certSign.showSig=Signatur anzeigen
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=\u039A\u03CC\u03BA\u03BA\u03B9\u03BD\u03BF
|
|||||||
green=\u03A0\u03C1\u03AC\u03C3\u03B9\u03BD\u03BF
|
green=\u03A0\u03C1\u03AC\u03C3\u03B9\u03BD\u03BF
|
||||||
blue=\u039C\u03C0\u03BB\u03AD
|
blue=\u039C\u03C0\u03BB\u03AD
|
||||||
custom=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE...
|
custom=\u03A0\u03C1\u03BF\u03C3\u03B1\u03C1\u03BC\u03BF\u03B3\u03AE...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=\u03A4\u03B1 \u03B4\u03B9\u03B1\u03C0\u03B9\u03C3\u03C4\u03B5\u03C5\u03C4\u03AE\u03C1\u03B9\u03B1 \u03AD\u03C7\u03BF\u03C5\u03BD \u03B1\u03BB\u03BB\u03AC\u03BE\u03B5\u03B9!
|
changedCredsMessage=\u03A4\u03B1 \u03B4\u03B9\u03B1\u03C0\u03B9\u03C3\u03C4\u03B5\u03C5\u03C4\u03AE\u03C1\u03B9\u03B1 \u03AD\u03C7\u03BF\u03C5\u03BD \u03B1\u03BB\u03BB\u03AC\u03BE\u03B5\u03B9!
|
||||||
notAuthenticatedMessage=\u039F \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03B4\u03B5\u03BD \u03AD\u03C7\u03B5\u03B9 \u03B1\u03C5\u03B8\u03B5\u03BD\u03C4\u03B9\u03BA\u03BF\u03C0\u03BF\u03B9\u03B7\u03B8\u03B5\u03AF.
|
notAuthenticatedMessage=\u039F \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7\u03C2 \u03B4\u03B5\u03BD \u03AD\u03C7\u03B5\u03B9 \u03B1\u03C5\u03B8\u03B5\u03BD\u03C4\u03B9\u03BA\u03BF\u03C0\u03BF\u03B9\u03B7\u03B8\u03B5\u03AF.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=\u039F \u03C4\u03C1\u03AD\u03C7\u03C9\u03BD \u03BA\u03C
|
|||||||
usernameExistsMessage=\u03A4\u03BF \u03BD\u03AD\u03BF \u03CC\u03BD\u03BF\u03BC\u03B1 \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7 \u03C5\u03C0\u03AC\u03C1\u03C7\u03B5\u03B9 \u03AE\u03B4\u03B7.
|
usernameExistsMessage=\u03A4\u03BF \u03BD\u03AD\u03BF \u03CC\u03BD\u03BF\u03BC\u03B1 \u03C7\u03C1\u03AE\u03C3\u03C4\u03B7 \u03C5\u03C0\u03AC\u03C1\u03C7\u03B5\u03B9 \u03AE\u03B4\u03B7.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=\u03A5\u03C0\u03BF\u03B2\u03BF\u03BB\u03AE
|
|||||||
certSign.title=\u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE \u03A0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03BF\u03CD
|
certSign.title=\u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE \u03A0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03BF\u03CD
|
||||||
certSign.header=\u03A5\u03C0\u03BF\u03B3\u03C1\u03AC\u03C8\u03C4\u03B5 \u03AD\u03BD\u03B1 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF PDF \u03BC\u03B5 \u03C4\u03BF \u03C0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03CC \u03C3\u03B1\u03C2 (\u0395\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1 \u03C3\u03B5 \u03B5\u03BE\u03AD\u03BB\u03B9\u03BE\u03B7)
|
certSign.header=\u03A5\u03C0\u03BF\u03B3\u03C1\u03AC\u03C8\u03C4\u03B5 \u03AD\u03BD\u03B1 \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF PDF \u03BC\u03B5 \u03C4\u03BF \u03C0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03CC \u03C3\u03B1\u03C2 (\u0395\u03C1\u03B3\u03B1\u03C3\u03AF\u03B1 \u03C3\u03B5 \u03B5\u03BE\u03AD\u03BB\u03B9\u03BE\u03B7)
|
||||||
certSign.selectPDF=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 PDF \u03B3\u03B9\u03B1 \u03C5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE:
|
certSign.selectPDF=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF\u03C5 PDF \u03B3\u03B9\u03B1 \u03C5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03C4\u03BF\u03C5 \u03B9\u03B4\u03B9\u03C9\u03C4\u03B9\u03BA\u03BF\u03CD \u03BA\u03BB\u03B5\u03B9\u03B4\u03B9\u03BF\u03CD \u03C3\u03B1\u03C2 (\u03BC\u03BF\u03C1\u03C6\u03AE PKCS#8, \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 .pem \u03AE .der):
|
certSign.selectKey=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03C4\u03BF\u03C5 \u03B9\u03B4\u03B9\u03C9\u03C4\u03B9\u03BA\u03BF\u03CD \u03BA\u03BB\u03B5\u03B9\u03B4\u03B9\u03BF\u03CD \u03C3\u03B1\u03C2 (\u03BC\u03BF\u03C1\u03C6\u03AE PKCS#8, \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 .pem \u03AE .der):
|
||||||
certSign.selectCert=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03C0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03BF\u03CD \u03C3\u03B1\u03C2 (\u03BC\u03BF\u03C1\u03C6\u03AE X.509, \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 .pem \u03AE .der):
|
certSign.selectCert=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF \u03C0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03BF\u03CD \u03C3\u03B1\u03C2 (\u03BC\u03BF\u03C1\u03C6\u03AE X.509, \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B5\u03AF\u03BD\u03B1\u03B9 .pem \u03AE .der):
|
||||||
certSign.selectP12=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF PKCS#12 Keystore (.p12 \u03AE .pfx) (\u03A0\u03C1\u03BF\u03B1\u03B9\u03C1\u03B5\u03C4\u03B9\u03BA\u03CC, \u03B5\u03AC\u03BD \u03C0\u03B1\u03C1\u03AD\u03C7\u03B5\u03C4\u03B1\u03B9, \u03B8\u03B1 \u03C0\u03C1\u03AD\u03C0\u03B5\u03B9 \u03BD\u03B1 \u03C0\u03B5\u03C1\u03B9\u03AD\u03C7\u03B5\u03B9 \u03C4\u03BF \u03B9\u03B4\u03B9\u03C9\u03C4\u03B9\u03BA\u03CC \u03BA\u03BB\u03B5\u03B9\u03B4\u03AF \u03BA\u03B1\u03B9 \u03C4\u03BF \u03C0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03CC \u03C3\u03B1\u03C2):
|
certSign.selectP12=\u0395\u03C0\u03B9\u03BB\u03AD\u03BE\u03C4\u03B5 \u03C4\u03BF \u03B1\u03C1\u03C7\u03B5\u03AF\u03BF PKCS#12 Keystore (.p12 \u03AE .pfx) (\u03A0\u03C1\u03BF\u03B1\u03B9\u03C1\u03B5\u03C4\u03B9\u03BA\u03CC, \u03B5\u03AC\u03BD \u03C0\u03B1\u03C1\u03AD\u03C7\u03B5\u03C4\u03B1\u03B9, \u03B8\u03B1 \u03C0\u03C1\u03AD\u03C0\u03B5\u03B9 \u03BD\u03B1 \u03C0\u03B5\u03C1\u03B9\u03AD\u03C7\u03B5\u03B9 \u03C4\u03BF \u03B9\u03B4\u03B9\u03C9\u03C4\u03B9\u03BA\u03CC \u03BA\u03BB\u03B5\u03B9\u03B4\u03AF \u03BA\u03B1\u03B9 \u03C4\u03BF \u03C0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03CC \u03C3\u03B1\u03C2):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=\u03A4\u03CD\u03C0\u03BF\u03C2 \u03A0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03BF\u03CD
|
certSign.certType=\u03A4\u03CD\u03C0\u03BF\u03C2 \u03A0\u03B9\u03C3\u03C4\u03BF\u03C0\u03BF\u03B9\u03B7\u03C4\u03B9\u03BA\u03BF\u03CD
|
||||||
certSign.password=\u0395\u03B9\u03C3\u03B1\u03B3\u03AC\u03B3\u03B5\u03C4\u03B5 \u03C4\u03BF\u03BD \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C4\u03BF\u03C5 Keystore \u03AE \u03C4\u03BF\u03C5 \u0399\u03B4\u03B9\u03C9\u03C4\u03B9\u03BA\u03BF\u03CD \u039A\u03BB\u03B5\u03B9\u03B4\u03B9\u03BF\u03CD (\u03B5\u03AC\u03BD \u03C5\u03C0\u03AC\u03C1\u03C7\u03B5\u03B9):
|
certSign.password=\u0395\u03B9\u03C3\u03B1\u03B3\u03AC\u03B3\u03B5\u03C4\u03B5 \u03C4\u03BF\u03BD \u03BA\u03C9\u03B4\u03B9\u03BA\u03CC \u03C4\u03BF\u03C5 Keystore \u03AE \u03C4\u03BF\u03C5 \u0399\u03B4\u03B9\u03C9\u03C4\u03B9\u03BA\u03BF\u03CD \u039A\u03BB\u03B5\u03B9\u03B4\u03B9\u03BF\u03CD (\u03B5\u03AC\u03BD \u03C5\u03C0\u03AC\u03C1\u03C7\u03B5\u03B9):
|
||||||
certSign.showSig=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 \u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03C2
|
certSign.showSig=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 \u03A5\u03C0\u03BF\u03B3\u03C1\u03B1\u03C6\u03AE\u03C2
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Red
|
|||||||
green=Green
|
green=Green
|
||||||
blue=Blue
|
blue=Blue
|
||||||
custom=Custom...
|
custom=Custom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Credentials changed!
|
changedCredsMessage=Credentials changed!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,30 @@ incorrectPasswordMessage=Current password is incorrect.
|
|||||||
usernameExistsMessage=New Username already exists.
|
usernameExistsMessage=New Username already exists.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.selectOperation=Select Operation
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +548,11 @@ scalePages.submit=Submit
|
|||||||
certSign.title=Certificate Signing
|
certSign.title=Certificate Signing
|
||||||
certSign.header=Sign a PDF with your certificate (Work in progress)
|
certSign.header=Sign a PDF with your certificate (Work in progress)
|
||||||
certSign.selectPDF=Select a PDF File for Signing:
|
certSign.selectPDF=Select a PDF File for Signing:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der):
|
certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der):
|
||||||
certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der):
|
certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der):
|
||||||
certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate):
|
certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Certificate Type
|
certSign.certType=Certificate Type
|
||||||
certSign.password=Enter Your Keystore or Private Key Password (If Any):
|
certSign.password=Enter Your Keystore or Private Key Password (If Any):
|
||||||
certSign.showSig=Show Signature
|
certSign.showSig=Show Signature
|
||||||
@@ -894,3 +922,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Red
|
|||||||
green=Green
|
green=Green
|
||||||
blue=Blue
|
blue=Blue
|
||||||
custom=Custom...
|
custom=Custom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Credentials changed!
|
changedCredsMessage=Credentials changed!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Current password is incorrect.
|
|||||||
usernameExistsMessage=New Username already exists.
|
usernameExistsMessage=New Username already exists.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Submit
|
|||||||
certSign.title=Certificate Signing
|
certSign.title=Certificate Signing
|
||||||
certSign.header=Sign a PDF with your certificate (Work in progress)
|
certSign.header=Sign a PDF with your certificate (Work in progress)
|
||||||
certSign.selectPDF=Select a PDF File for Signing:
|
certSign.selectPDF=Select a PDF File for Signing:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der):
|
certSign.selectKey=Select Your Private Key File (PKCS#8 format, could be .pem or .der):
|
||||||
certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der):
|
certSign.selectCert=Select Your Certificate File (X.509 format, could be .pem or .der):
|
||||||
certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate):
|
certSign.selectP12=Select Your PKCS#12 Keystore File (.p12 or .pfx) (Optional, If provided, it should contain your private key and certificate):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Certificate Type
|
certSign.certType=Certificate Type
|
||||||
certSign.password=Enter Your Keystore or Private Key Password (If Any):
|
certSign.password=Enter Your Keystore or Private Key Password (If Any):
|
||||||
certSign.showSig=Show Signature
|
certSign.showSig=Show Signature
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Rojo
|
|||||||
green=Verde
|
green=Verde
|
||||||
blue=Azul
|
blue=Azul
|
||||||
custom=Personalizado...
|
custom=Personalizado...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Se cambiaron las credenciales!
|
changedCredsMessage=Se cambiaron las credenciales!
|
||||||
notAuthenticatedMessage=Usuario no autentificado.
|
notAuthenticatedMessage=Usuario no autentificado.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=La contraseña actual no es correcta.
|
|||||||
usernameExistsMessage=El nuevo nombre de usuario está en uso.
|
usernameExistsMessage=El nuevo nombre de usuario está en uso.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Entregar
|
|||||||
certSign.title=Firma con certificado
|
certSign.title=Firma con certificado
|
||||||
certSign.header=Firmar un PDF con su certificado (en desarrollo)
|
certSign.header=Firmar un PDF con su certificado (en desarrollo)
|
||||||
certSign.selectPDF=Seleccione un archivo PDF para firmar:
|
certSign.selectPDF=Seleccione un archivo PDF para firmar:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Seleccione su archivo de clave privada (formato PKCS#8, podría ser .pem o .der):
|
certSign.selectKey=Seleccione su archivo de clave privada (formato PKCS#8, podría ser .pem o .der):
|
||||||
certSign.selectCert=Seleccione su archivo de certificado (formato X.509, podría ser .pem o .der):
|
certSign.selectCert=Seleccione su archivo de certificado (formato X.509, podría ser .pem o .der):
|
||||||
certSign.selectP12=Seleccione su archivo de almacén de claves PKCS#12 (.p12 o .pfx) (Opcional, si se proporciona, debe contener su clave privada y certificado):
|
certSign.selectP12=Seleccione su archivo de almacén de claves PKCS#12 (.p12 o .pfx) (Opcional, si se proporciona, debe contener su clave privada y certificado):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Tipo de certificado
|
certSign.certType=Tipo de certificado
|
||||||
certSign.password=Introduzca su almacén de claves o contraseña de clave privada (si corresponde):
|
certSign.password=Introduzca su almacén de claves o contraseña de clave privada (si corresponde):
|
||||||
certSign.showSig=Mostrar firma
|
certSign.showSig=Mostrar firma
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Divisiones Verticales
|
|||||||
split-by-sections.horizontal.placeholder=Introduzca el número de divisiones horizontales
|
split-by-sections.horizontal.placeholder=Introduzca el número de divisiones horizontales
|
||||||
split-by-sections.vertical.placeholder=Introduzca el número de divisiones verticales
|
split-by-sections.vertical.placeholder=Introduzca el número de divisiones verticales
|
||||||
split-by-sections.submit=Dividir PDF
|
split-by-sections.submit=Dividir PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Gorria
|
|||||||
green=Berdea
|
green=Berdea
|
||||||
blue=Urdina
|
blue=Urdina
|
||||||
custom=Pertsonalizatu...
|
custom=Pertsonalizatu...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Credentials changed!
|
changedCredsMessage=Credentials changed!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Current password is incorrect.
|
|||||||
usernameExistsMessage=New Username already exists.
|
usernameExistsMessage=New Username already exists.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Entregatu
|
|||||||
certSign.title=Ziurtagiriaren sinadura
|
certSign.title=Ziurtagiriaren sinadura
|
||||||
certSign.header=Sinatu PDF bat haren ziurtagiriarekin (lanean)
|
certSign.header=Sinatu PDF bat haren ziurtagiriarekin (lanean)
|
||||||
certSign.selectPDF=Hautatu PDF fitxategi bat sinatzeko:
|
certSign.selectPDF=Hautatu PDF fitxategi bat sinatzeko:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Hautatu gako pribatuko fitxategia (PKCS#8 formatua, .pem edo .der izan liteke):
|
certSign.selectKey=Hautatu gako pribatuko fitxategia (PKCS#8 formatua, .pem edo .der izan liteke):
|
||||||
certSign.selectCert=Hautatu ziurtagiridun fitxategia (X.509 formatua, .pem edo .der izan liteke):
|
certSign.selectCert=Hautatu ziurtagiridun fitxategia (X.509 formatua, .pem edo .der izan liteke):
|
||||||
certSign.selectP12=Hautatu gakoak gordetzeko fitxategia PKCS#12 (.p12 o .pfx) (Aukerakoa, ematen bada, gako pribatua eta ziurtagiria izan beharko ditu):
|
certSign.selectP12=Hautatu gakoak gordetzeko fitxategia PKCS#12 (.p12 o .pfx) (Aukerakoa, ematen bada, gako pribatua eta ziurtagiria izan beharko ditu):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Ziurtagiri-mota
|
certSign.certType=Ziurtagiri-mota
|
||||||
certSign.password=Sartu zure gakoen biltegia edo gako pribatuko pasahitza (hala badagokio):
|
certSign.password=Sartu zure gakoen biltegia edo gako pribatuko pasahitza (hala badagokio):
|
||||||
certSign.showSig=Erakutsi sinadura
|
certSign.showSig=Erakutsi sinadura
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Rouge
|
|||||||
green=Vert
|
green=Vert
|
||||||
blue=Bleu
|
blue=Bleu
|
||||||
custom=Personnalisé\u2026
|
custom=Personnalisé\u2026
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Les identifiants ont été mis à jour\u00a0!
|
changedCredsMessage=Les identifiants ont été mis à jour\u00a0!
|
||||||
notAuthenticatedMessage=Utilisateur non authentifié.
|
notAuthenticatedMessage=Utilisateur non authentifié.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Le mot de passe actuel est incorrect.
|
|||||||
usernameExistsMessage=Le nouveau nom d\u2019utilisateur existe déjà.
|
usernameExistsMessage=Le nouveau nom d\u2019utilisateur existe déjà.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Ajuster
|
|||||||
certSign.title=Signer avec un certificat
|
certSign.title=Signer avec un certificat
|
||||||
certSign.header=Signer avec un certificat (Travail en cours)
|
certSign.header=Signer avec un certificat (Travail en cours)
|
||||||
certSign.selectPDF=PDF à signer
|
certSign.selectPDF=PDF à signer
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Fichier de clé privée (format PKCS#8, peut être .pem ou .der)
|
certSign.selectKey=Fichier de clé privée (format PKCS#8, peut être .pem ou .der)
|
||||||
certSign.selectCert=Fichier de certificat (format X.509, peut être .pem ou .der)
|
certSign.selectCert=Fichier de certificat (format X.509, peut être .pem ou .der)
|
||||||
certSign.selectP12=Fichier keystore de clés PKCS#12 (.p12 ou .pfx) (facultatif, s\u2019il n\u2019est fourni, il doit contenir votre clé privée et votre certificat)
|
certSign.selectP12=Fichier keystore de clés PKCS#12 (.p12 ou .pfx) (facultatif, s\u2019il n\u2019est fourni, il doit contenir votre clé privée et votre certificat)
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Type de certificat
|
certSign.certType=Type de certificat
|
||||||
certSign.password=Mot de passe keystore ou clé privée le cas échéant
|
certSign.password=Mot de passe keystore ou clé privée le cas échéant
|
||||||
certSign.showSig=Afficher la signature
|
certSign.showSig=Afficher la signature
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Divisions verticales
|
|||||||
split-by-sections.horizontal.placeholder=Saisir le nombre de divisions horizontales
|
split-by-sections.horizontal.placeholder=Saisir le nombre de divisions horizontales
|
||||||
split-by-sections.vertical.placeholder=Entrer le nombre de divisions verticales
|
split-by-sections.vertical.placeholder=Entrer le nombre de divisions verticales
|
||||||
split-by-sections.submit=Diviser le PDF
|
split-by-sections.submit=Diviser le PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=लाल
|
|||||||
green=हरा
|
green=हरा
|
||||||
blue=नीला
|
blue=नीला
|
||||||
custom=कस्टम...
|
custom=कस्टम...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=क्रेडेंशियल्स बदल दी गईं!
|
changedCredsMessage=क्रेडेंशियल्स बदल दी गईं!
|
||||||
notAuthenticatedMessage=उपयोगकर्ता प्रमाणित नहीं है।
|
notAuthenticatedMessage=उपयोगकर्ता प्रमाणित नहीं है।
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=वर्तमान पासवर्ड गलत
|
|||||||
usernameExistsMessage=नया उपयोगकर्ता नाम पहले से मौजूद है।
|
usernameExistsMessage=नया उपयोगकर्ता नाम पहले से मौजूद है।
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=प्रस्तुत करें
|
|||||||
certSign.title=प्रमाणपत्र साइनिंग
|
certSign.title=प्रमाणपत्र साइनिंग
|
||||||
certSign.header=अपने प्रमाणपत्र के साथ एक पीडीएफ़ पर हस्ताक्षर करें (काम जारी है)
|
certSign.header=अपने प्रमाणपत्र के साथ एक पीडीएफ़ पर हस्ताक्षर करें (काम जारी है)
|
||||||
certSign.selectPDF=साइन करने के लिए एक पीडीएफ़ फ़ाइल का चयन करें:
|
certSign.selectPDF=साइन करने के लिए एक पीडीएफ़ फ़ाइल का चयन करें:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=अपनी निजी कुंजी फ़ाइल का चयन करें (PKCS#8 प्रारूप, .pem या .der हो सकता है):
|
certSign.selectKey=अपनी निजी कुंजी फ़ाइल का चयन करें (PKCS#8 प्रारूप, .pem या .der हो सकता है):
|
||||||
certSign.selectCert=अपनी प्रमाणपत्र फ़ाइल का चयन करें (X.509 प्रारूप, .pem या .der हो सकता है):
|
certSign.selectCert=अपनी प्रमाणपत्र फ़ाइल का चयन करें (X.509 प्रारूप, .pem या .der हो सकता है):
|
||||||
certSign.selectP12=अपनी PKCS#12 कीस्टोर फ़ाइल का चयन करें (.p12 या .pfx) (वैकल्पिक, यदि प्रदान की गई हो, तो इसमें आपकी निजी कुंजी और प्रमाणपत्र होना चाहिए):
|
certSign.selectP12=अपनी PKCS#12 कीस्टोर फ़ाइल का चयन करें (.p12 या .pfx) (वैकल्पिक, यदि प्रदान की गई हो, तो इसमें आपकी निजी कुंजी और प्रमाणपत्र होना चाहिए):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=प्रमाणपत्र प्रकार
|
certSign.certType=प्रमाणपत्र प्रकार
|
||||||
certSign.password=अपनी कीस्टोर या निजी कुंजी पासवर्ड दर्ज करें (यदि कोई हो):
|
certSign.password=अपनी कीस्टोर या निजी कुंजी पासवर्ड दर्ज करें (यदि कोई हो):
|
||||||
certSign.showSig=हस्ताक्षर दिखाएं
|
certSign.showSig=हस्ताक्षर दिखाएं
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=लंबवत विभाजन
|
|||||||
split-by-sections.horizontal.placeholder=क्षैतिज विभाजन की संख्या दर्ज करें
|
split-by-sections.horizontal.placeholder=क्षैतिज विभाजन की संख्या दर्ज करें
|
||||||
split-by-sections.vertical.placeholder=लंबवत विभाजन की संख्या दर्ज करें
|
split-by-sections.vertical.placeholder=लंबवत विभाजन की संख्या दर्ज करें
|
||||||
split-by-sections.submit=PDF को विभाजित करें
|
split-by-sections.submit=PDF को विभाजित करें
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Piros
|
|||||||
green=Zöld
|
green=Zöld
|
||||||
blue=Kék
|
blue=Kék
|
||||||
custom=Egyedi...
|
custom=Egyedi...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=A hitelek megváltoztak!
|
changedCredsMessage=A hitelek megváltoztak!
|
||||||
notAuthenticatedMessage=Felhasználó nincs hitelesítve.
|
notAuthenticatedMessage=Felhasználó nincs hitelesítve.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=A jelenlegi jelszó helytelen.
|
|||||||
usernameExistsMessage=Az új felhasználónév már létezik.
|
usernameExistsMessage=Az új felhasználónév már létezik.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Küldés
|
|||||||
certSign.title=Tanúsítvánnyal történő aláírás
|
certSign.title=Tanúsítvánnyal történő aláírás
|
||||||
certSign.header=Aláírás PDF tanúsítvánnyal (fejlesztés alatt)
|
certSign.header=Aláírás PDF tanúsítvánnyal (fejlesztés alatt)
|
||||||
certSign.selectPDF=Válasszon PDF fájlt az aláíráshoz:
|
certSign.selectPDF=Válasszon PDF fájlt az aláíráshoz:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Válassza ki a saját kulcsfájlját (PKCS#8 formátum, lehet .pem vagy .der kiterjesztésű):
|
certSign.selectKey=Válassza ki a saját kulcsfájlját (PKCS#8 formátum, lehet .pem vagy .der kiterjesztésű):
|
||||||
certSign.selectCert=Válassza ki a tanúsítványfájlját (X.509 formátum, lehet .pem vagy .der kiterjesztésű):
|
certSign.selectCert=Válassza ki a tanúsítványfájlját (X.509 formátum, lehet .pem vagy .der kiterjesztésű):
|
||||||
certSign.selectP12=Válassza ki a PKCS#12 kulcstár fájlját (.p12 vagy .pfx) (Opcionális, ha rendelkezésre áll, tartalmaznia kell a privát kulcsot és a tanúsítványt.):
|
certSign.selectP12=Válassza ki a PKCS#12 kulcstár fájlját (.p12 vagy .pfx) (Opcionális, ha rendelkezésre áll, tartalmaznia kell a privát kulcsot és a tanúsítványt.):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Tanúsítvány típusa
|
certSign.certType=Tanúsítvány típusa
|
||||||
certSign.password=Adja meg a kulcstár vagy a privát kulcs jelszavát (ha van):
|
certSign.password=Adja meg a kulcstár vagy a privát kulcs jelszavát (ha van):
|
||||||
certSign.showSig=Aláírás megjelenítése
|
certSign.showSig=Aláírás megjelenítése
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vízszintes szakaszok
|
|||||||
split-by-sections.horizontal.placeholder=Adja meg a vízszintes szakaszok számát
|
split-by-sections.horizontal.placeholder=Adja meg a vízszintes szakaszok számát
|
||||||
split-by-sections.vertical.placeholder=Adja meg a függőleges szakaszok számát
|
split-by-sections.vertical.placeholder=Adja meg a függőleges szakaszok számát
|
||||||
split-by-sections.submit=Felosztás
|
split-by-sections.submit=Felosztás
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl=right to left)
|
# the direction that the language is written (ltr=left to right, rtl=right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Merah
|
|||||||
green=Hijau
|
green=Hijau
|
||||||
blue=Biru
|
blue=Biru
|
||||||
custom=Kustom...
|
custom=Kustom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Kredensial berubah!!
|
changedCredsMessage=Kredensial berubah!!
|
||||||
notAuthenticatedMessage=Pengguna tidak ter-autentikasi.
|
notAuthenticatedMessage=Pengguna tidak ter-autentikasi.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Kata sandi saat ini salah.
|
|||||||
usernameExistsMessage=Nama pengguna baru sudah ada.
|
usernameExistsMessage=Nama pengguna baru sudah ada.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Kirim
|
|||||||
certSign.title=Penandatanganan Sertifikat
|
certSign.title=Penandatanganan Sertifikat
|
||||||
certSign.header=Menandatangani PDF dengan sertifikat Anda (Sedang dalam proses)
|
certSign.header=Menandatangani PDF dengan sertifikat Anda (Sedang dalam proses)
|
||||||
certSign.selectPDF=Pilih Berkas PDF untuk Penandatanganan:
|
certSign.selectPDF=Pilih Berkas PDF untuk Penandatanganan:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Pilih Berkas Kunci Pribadi Anda (format PKCS # 8, bisa .pem atau .der):
|
certSign.selectKey=Pilih Berkas Kunci Pribadi Anda (format PKCS # 8, bisa .pem atau .der):
|
||||||
certSign.selectCert=Pilih Berkas Sertifikat Anda (format X.509, bisa .pem atau .der):
|
certSign.selectCert=Pilih Berkas Sertifikat Anda (format X.509, bisa .pem atau .der):
|
||||||
certSign.selectP12=Pilih Berkas Keystore PKCS #12 Anda (.p12 atau .pfx) (Opsional, Jika disediakan, berkas tersebut harus berisi kunci pribadi dan sertifikat Anda):
|
certSign.selectP12=Pilih Berkas Keystore PKCS #12 Anda (.p12 atau .pfx) (Opsional, Jika disediakan, berkas tersebut harus berisi kunci pribadi dan sertifikat Anda):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Jenis Sertifikat
|
certSign.certType=Jenis Sertifikat
|
||||||
certSign.password=Masukkan Kata Sandi Kunci atau Kunci Pribadi Anda (Jika Ada):
|
certSign.password=Masukkan Kata Sandi Kunci atau Kunci Pribadi Anda (Jika Ada):
|
||||||
certSign.showSig=Tampilkan Tanda Tangan
|
certSign.showSig=Tampilkan Tanda Tangan
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Pembagian Vertikal
|
|||||||
split-by-sections.horizontal.placeholder=Input angka untuk pembagian horizontal
|
split-by-sections.horizontal.placeholder=Input angka untuk pembagian horizontal
|
||||||
split-by-sections.vertical.placeholder=Input angka untuk pembagian vertikal
|
split-by-sections.vertical.placeholder=Input angka untuk pembagian vertikal
|
||||||
split-by-sections.submit=Pisahkan PDF
|
split-by-sections.submit=Pisahkan PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Rosso
|
|||||||
green=Verde
|
green=Verde
|
||||||
blue=Blu
|
blue=Blu
|
||||||
custom=Personalizzato
|
custom=Personalizzato
|
||||||
|
WorkInProgess=Lavori in corso, potrebbe non funzionare o essere difettoso, segnalare eventuali problemi!
|
||||||
|
poweredBy=Alimentato da
|
||||||
|
|
||||||
changedCredsMessage=Credenziali cambiate!
|
changedCredsMessage=Credenziali cambiate!
|
||||||
notAuthenticatedMessage=Utente non autenticato.
|
notAuthenticatedMessage=Utente non autenticato.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=La password attuale non è corretta.
|
|||||||
usernameExistsMessage=Il nuovo nome utente esiste già.
|
usernameExistsMessage=Il nuovo nome utente esiste già.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Caricamento personalizzato
|
||||||
|
pipeline.configureButton=Configura
|
||||||
|
pipeline.defaultOption=Personalizzato
|
||||||
|
pipeline.submitButton=Invia
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Configurazione Pipeline
|
||||||
|
pipelineOptions.pipelineNameLabel=Nome della Pipeline
|
||||||
|
pipelineOptions.saveSettings=Salva Impostazioni
|
||||||
|
pipelineOptions.pipelineNamePrompt=Inserisci qui il nome della pipeline
|
||||||
|
pipelineOptions.addOperationButton=Aggiungi operazione
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Convalidare
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Invia
|
|||||||
certSign.title=Firma del certificato
|
certSign.title=Firma del certificato
|
||||||
certSign.header=Firma un PDF con il tuo certificato (Lavoro in corso)
|
certSign.header=Firma un PDF con il tuo certificato (Lavoro in corso)
|
||||||
certSign.selectPDF=Seleziona un file PDF per la firma:
|
certSign.selectPDF=Seleziona un file PDF per la firma:
|
||||||
|
certSign.jksNote=Nota: se il tipo di certificato non è elencato di seguito, convertilo in un file Java Keystore (.jks) utilizzando lo strumento da riga di comando keytool. Quindi, scegli l'opzione del file .jks di seguito.
|
||||||
certSign.selectKey=Seleziona il file della tua chiave privata (formato PKCS#8, potrebbe essere .pem o .der):
|
certSign.selectKey=Seleziona il file della tua chiave privata (formato PKCS#8, potrebbe essere .pem o .der):
|
||||||
certSign.selectCert=Seleziona il tuo file di certificato (formato X.509, potrebbe essere .pem o .der):
|
certSign.selectCert=Seleziona il tuo file di certificato (formato X.509, potrebbe essere .pem o .der):
|
||||||
certSign.selectP12=Selezionare il file keystore PKCS#12 (.p12 o .pfx) (facoltativo, se fornito, dovrebbe contenere la chiave privata e il certificato):
|
certSign.selectP12=Selezionare il file keystore PKCS#12 (.p12 o .pfx) (facoltativo, se fornito, dovrebbe contenere la chiave privata e il certificato):
|
||||||
|
certSign.selectJKS=Seleziona il tuo file Java Keystore (.jks o .keystore):
|
||||||
certSign.certType=Tipo di certificato
|
certSign.certType=Tipo di certificato
|
||||||
certSign.password=Inserisci la tua password dell'archivio chiavi o della chiave privata (se presente):
|
certSign.password=Inserisci la tua password dell'archivio chiavi o della chiave privata (se presente):
|
||||||
certSign.showSig=Mostra firma
|
certSign.showSig=Mostra firma
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Divisioni verticali
|
|||||||
split-by-sections.horizontal.placeholder=Inserire il numero di divisioni orizzontali
|
split-by-sections.horizontal.placeholder=Inserire il numero di divisioni orizzontali
|
||||||
split-by-sections.vertical.placeholder=Inserire il numero di divisioni verticali
|
split-by-sections.vertical.placeholder=Inserire il numero di divisioni verticali
|
||||||
split-by-sections.submit=Dividi PDF
|
split-by-sections.submit=Dividi PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenze
|
||||||
|
licenses.title=Licenze di terze parti
|
||||||
|
licenses.header=Licenze di terze parti
|
||||||
|
licenses.module=Modulo
|
||||||
|
licenses.version=Versione
|
||||||
|
licenses.license=Licenza
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=赤
|
|||||||
green=緑
|
green=緑
|
||||||
blue=青
|
blue=青
|
||||||
custom=カスタム...
|
custom=カスタム...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=資格情報が変更されました!
|
changedCredsMessage=資格情報が変更されました!
|
||||||
notAuthenticatedMessage=ユーザーが認証されていません。
|
notAuthenticatedMessage=ユーザーが認証されていません。
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=現在のパスワードが正しくありません。
|
|||||||
usernameExistsMessage=新しいユーザー名はすでに存在します。
|
usernameExistsMessage=新しいユーザー名はすでに存在します。
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=送信
|
|||||||
certSign.title=証明書による署名
|
certSign.title=証明書による署名
|
||||||
certSign.header=証明書を使用してPDFに署名します。 (制作中)
|
certSign.header=証明書を使用してPDFに署名します。 (制作中)
|
||||||
certSign.selectPDF=署名するPDFファイルを選択:
|
certSign.selectPDF=署名するPDFファイルを選択:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=秘密キーファイルを選択 (PKCS#8形式、.pemまたは.der) :
|
certSign.selectKey=秘密キーファイルを選択 (PKCS#8形式、.pemまたは.der) :
|
||||||
certSign.selectCert=証明書ファイルを選択 (X.509形式、.pemまたは.der) :
|
certSign.selectCert=証明書ファイルを選択 (X.509形式、.pemまたは.der) :
|
||||||
certSign.selectP12=PKCS#12キーストアファイルを選択 (.p12または.pfx) (オプション。指定する場合は秘密キーと証明書が含まれている必要があります。):
|
certSign.selectP12=PKCS#12キーストアファイルを選択 (.p12または.pfx) (オプション。指定する場合は秘密キーと証明書が含まれている必要があります。):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=証明書の種類
|
certSign.certType=証明書の種類
|
||||||
certSign.password=キーストアまたは秘密キーのパスワードを入力 (ある場合) :
|
certSign.password=キーストアまたは秘密キーのパスワードを入力 (ある場合) :
|
||||||
certSign.showSig=署名を表示
|
certSign.showSig=署名を表示
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Red
|
|||||||
green=Green
|
green=Green
|
||||||
blue=Blue
|
blue=Blue
|
||||||
custom=Custom...
|
custom=Custom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=계정 정보 변경 성공!
|
changedCredsMessage=계정 정보 변경 성공!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=현재 비밀번호가 틀립니다.
|
|||||||
usernameExistsMessage=새 사용자명이 이미 존재합니다.
|
usernameExistsMessage=새 사용자명이 이미 존재합니다.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=제출
|
|||||||
certSign.title=인증서로 서명
|
certSign.title=인증서로 서명
|
||||||
certSign.header=인증서로 PDF 문서에 서명 (개발 중)
|
certSign.header=인증서로 PDF 문서에 서명 (개발 중)
|
||||||
certSign.selectPDF=서명할 PDF 문서를 선택합니다:
|
certSign.selectPDF=서명할 PDF 문서를 선택합니다:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=개인 키 파일을 선택합니다 (PKCS#8 형식, .pem 또는 .der):
|
certSign.selectKey=개인 키 파일을 선택합니다 (PKCS#8 형식, .pem 또는 .der):
|
||||||
certSign.selectCert=인증서 파일을 선택합니다 (X.509 형식, .pem 또는 .der):
|
certSign.selectCert=인증서 파일을 선택합니다 (X.509 형식, .pem 또는 .der):
|
||||||
certSign.selectP12=PKCS#12 키 저장소 파일을 선택합니다 (.p12 or .pfx) (선택 사항, 선택할 경우, 개인 키와 인증서를 포함하고 있어야 합니다):
|
certSign.selectP12=PKCS#12 키 저장소 파일을 선택합니다 (.p12 or .pfx) (선택 사항, 선택할 경우, 개인 키와 인증서를 포함하고 있어야 합니다):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=인증서 유형
|
certSign.certType=인증서 유형
|
||||||
certSign.password=키 저장소 또는 개인 키 비밀번호를 입력합니다 (있는 경우):
|
certSign.password=키 저장소 또는 개인 키 비밀번호를 입력합니다 (있는 경우):
|
||||||
certSign.showSig=서명 보기
|
certSign.showSig=서명 보기
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Red
|
|||||||
green=Green
|
green=Green
|
||||||
blue=Blue
|
blue=Blue
|
||||||
custom=Custom...
|
custom=Custom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Credentials changed!
|
changedCredsMessage=Credentials changed!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Current password is incorrect.
|
|||||||
usernameExistsMessage=New Username already exists.
|
usernameExistsMessage=New Username already exists.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Indienen
|
|||||||
certSign.title=Certificaat ondertekening
|
certSign.title=Certificaat ondertekening
|
||||||
certSign.header=Onderteken een PDF met je certificaat (in ontwikkeling)
|
certSign.header=Onderteken een PDF met je certificaat (in ontwikkeling)
|
||||||
certSign.selectPDF=Selecteer een PDF-bestand voor ondertekening:
|
certSign.selectPDF=Selecteer een PDF-bestand voor ondertekening:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Selecteer je privésleutelbestand (PKCS#8 formaat, kan .pem of .der zijn):
|
certSign.selectKey=Selecteer je privésleutelbestand (PKCS#8 formaat, kan .pem of .der zijn):
|
||||||
certSign.selectCert=Selecteer je certificaatbestand (X.509 formaat, kan .pem of .der zijn):
|
certSign.selectCert=Selecteer je certificaatbestand (X.509 formaat, kan .pem of .der zijn):
|
||||||
certSign.selectP12=Selecteer je PKCS#12 Sleutelopslagbestand (.p12 of .pfx) (Optioneel, indien verstrekt, moet het je privésleutel en certificaat bevatten):
|
certSign.selectP12=Selecteer je PKCS#12 Sleutelopslagbestand (.p12 of .pfx) (Optioneel, indien verstrekt, moet het je privésleutel en certificaat bevatten):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Certificaattype
|
certSign.certType=Certificaattype
|
||||||
certSign.password=Voer je sleutelopslag of privésleutel wachtwoord in (indien van toepassing):
|
certSign.password=Voer je sleutelopslag of privésleutel wachtwoord in (indien van toepassing):
|
||||||
certSign.showSig=Toon handtekening
|
certSign.showSig=Toon handtekening
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Red
|
|||||||
green=Green
|
green=Green
|
||||||
blue=Blue
|
blue=Blue
|
||||||
custom=Custom...
|
custom=Custom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Credentials changed!
|
changedCredsMessage=Credentials changed!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Current password is incorrect.
|
|||||||
usernameExistsMessage=New Username already exists.
|
usernameExistsMessage=New Username already exists.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Wykonaj
|
|||||||
certSign.title=Podpisywanie certyfikatem
|
certSign.title=Podpisywanie certyfikatem
|
||||||
certSign.header=Podpisz dokument PDF certyfikatem prywatnym (moduł w budowie)
|
certSign.header=Podpisz dokument PDF certyfikatem prywatnym (moduł w budowie)
|
||||||
certSign.selectPDF=Wybierz dokument PDF do podpisania:
|
certSign.selectPDF=Wybierz dokument PDF do podpisania:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Wybierz plik klucza prywatnego (format PKCS#8, może to być .pem lub .der):
|
certSign.selectKey=Wybierz plik klucza prywatnego (format PKCS#8, może to być .pem lub .der):
|
||||||
certSign.selectCert=Wybierz plik certyfikatu (format X.509, może to być .pem lub .der):
|
certSign.selectCert=Wybierz plik certyfikatu (format X.509, może to być .pem lub .der):
|
||||||
certSign.selectP12=Wybierz plik magazynu kluczy PKCS#12 (.p12 lub .pfx) (opcjonalnie, jeśli jest podany, powinien zawierać klucz prywatny i certyfikat):
|
certSign.selectP12=Wybierz plik magazynu kluczy PKCS#12 (.p12 lub .pfx) (opcjonalnie, jeśli jest podany, powinien zawierać klucz prywatny i certyfikat):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Typ certyfikatu
|
certSign.certType=Typ certyfikatu
|
||||||
certSign.password=Wprowadź hasło do magazynu kluczy lub klucza prywatnego (jeśli istnieje):
|
certSign.password=Wprowadź hasło do magazynu kluczy lub klucza prywatnego (jeśli istnieje):
|
||||||
certSign.showSig=Wyświetl podpis
|
certSign.showSig=Wyświetl podpis
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Red
|
|||||||
green=Green
|
green=Green
|
||||||
blue=Blue
|
blue=Blue
|
||||||
custom=Custom...
|
custom=Custom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Credentials changed!
|
changedCredsMessage=Credentials changed!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Current password is incorrect.
|
|||||||
usernameExistsMessage=New Username already exists.
|
usernameExistsMessage=New Username already exists.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Отправить
|
|||||||
certSign.title=Подписание сертификата
|
certSign.title=Подписание сертификата
|
||||||
certSign.header=Подпишите PDF своим сертификатом (работа в процессе)
|
certSign.header=Подпишите PDF своим сертификатом (работа в процессе)
|
||||||
certSign.selectPDF=Выберите файл PDF для подписи:
|
certSign.selectPDF=Выберите файл PDF для подписи:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Выберите файл закрытого ключа (формат PKCS#8, может быть .pem или .der):
|
certSign.selectKey=Выберите файл закрытого ключа (формат PKCS#8, может быть .pem или .der):
|
||||||
certSign.selectCert=Выберите файл сертификата (формат X.509, может быть .pem или .der):
|
certSign.selectCert=Выберите файл сертификата (формат X.509, может быть .pem или .der):
|
||||||
certSign.selectP12=Выберите файл хранилища ключей PKCS#12 (.p12 или .pfx) (необязательно, если он предоставлен, он должен содержать ваш закрытый ключ и сертификат):
|
certSign.selectP12=Выберите файл хранилища ключей PKCS#12 (.p12 или .pfx) (необязательно, если он предоставлен, он должен содержать ваш закрытый ключ и сертификат):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Тип сертификата
|
certSign.certType=Тип сертификата
|
||||||
certSign.password=Введите пароль от хранилища ключей или личного ключа (если есть):
|
certSign.password=Введите пароль от хранилища ключей или личного ключа (если есть):
|
||||||
certSign.showSig=Показать подпись
|
certSign.showSig=Показать подпись
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
934
src/main/resources/messages_sr-Latn-RS.properties
Normal file
934
src/main/resources/messages_sr-Latn-RS.properties
Normal file
@@ -0,0 +1,934 @@
|
|||||||
|
###########
|
||||||
|
# Generic #
|
||||||
|
###########
|
||||||
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
|
language.direction=ltr
|
||||||
|
|
||||||
|
pdfPrompt=Odaberi PDF(ove)
|
||||||
|
multiPdfPrompt=Odaberi PDF-ove (2+)
|
||||||
|
multiPdfDropPrompt=Odaberi (prevuci i pusti ) sve PDF-ove koji su vam potrebni
|
||||||
|
imgPrompt=Odaberi sliku (slike)
|
||||||
|
genericSubmit=Prihvatiti
|
||||||
|
processTimeWarning=Warning:Upozorenje: Ovaj proces može trajati i do minut, u zavisnosti od veličine dokumenta
|
||||||
|
pageOrderPrompt=Prilagođeni redosled stranica (unesi listu brojeva stranica ili funkcija, kao što su 2n+1, razdvojene zarezima) :
|
||||||
|
goToPage=Idi
|
||||||
|
true=Tačno
|
||||||
|
false=Netačno
|
||||||
|
unknown=Nepoznato
|
||||||
|
save=Sačuvaj
|
||||||
|
close=Zatvori
|
||||||
|
filesSelected=odabrani fajlovi
|
||||||
|
noFavourites=Nema dodatih favorita
|
||||||
|
bored=Da li ti je dosadno dok čekaš?
|
||||||
|
alphabet=Alfabet
|
||||||
|
downloadPdf=Skini PDF
|
||||||
|
text=Tekst
|
||||||
|
font=Font
|
||||||
|
selectFillter=-- Izaberi --
|
||||||
|
pageNum=Broj Strane
|
||||||
|
sizes.small=Malo
|
||||||
|
sizes.medium=Srednje
|
||||||
|
sizes.large=Veliko
|
||||||
|
sizes.x-large=X-Veliko
|
||||||
|
error.pdfPassword=PDF dokument je šifrovan i lozinka nije data ili je netačna
|
||||||
|
delete=Obriši
|
||||||
|
username=Korisničko ime
|
||||||
|
password=Šifra
|
||||||
|
welcome=Dobrodošli
|
||||||
|
property=Svojstvo
|
||||||
|
black=Crno
|
||||||
|
white=Belo
|
||||||
|
red=Crveno
|
||||||
|
green=Zeleno
|
||||||
|
blue=Plavo
|
||||||
|
custom=Prilagođeno...
|
||||||
|
WorkInProgess=Radovi u toku, možda neće raditi ili će biti grešaka, molimo prijavite sve probleme !
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
|
changedCredsMessage=Podaci za prijavu uspešno promenjeni!
|
||||||
|
notAuthenticatedMessage=Korisnik nije autentifikovan.
|
||||||
|
userNotFoundMessage=Korisnik nije pronađen.
|
||||||
|
incorrectPasswordMessage=Trenutna šifra je netačna.
|
||||||
|
usernameExistsMessage=Novi korisnik već postoji
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Meni za Pipeline (Alfa verzija)
|
||||||
|
pipeline.uploadButton=Postavi prilagođeno
|
||||||
|
pipeline.configureButton=Konfiguriši
|
||||||
|
pipeline.defaultOption=Prilagođeno
|
||||||
|
pipeline.submitButton=Pošalji
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Konfiguracija Pipeline-a
|
||||||
|
pipelineOptions.pipelineNameLabel=Ime Pipeline-a
|
||||||
|
pipelineOptions.saveSettings=Sačuvaj podešavanja
|
||||||
|
pipelineOptions.pipelineNamePrompt=Unesite ime pipeline-a ovde
|
||||||
|
pipelineOptions.addOperationButton=Dodaj operaciju
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Preuzmi
|
||||||
|
pipelineOptions.validateButton=Proveri
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#############
|
||||||
|
# NAVBAR #
|
||||||
|
#############
|
||||||
|
navbar.convert=Konvertuj
|
||||||
|
navbar.security=Bezbednost
|
||||||
|
navbar.other=Razno
|
||||||
|
navbar.darkmode=Tamni režim
|
||||||
|
navbar.pageOps=Operacije stranice
|
||||||
|
navbar.settings=Podešavanja
|
||||||
|
|
||||||
|
#############
|
||||||
|
# SETTINGS #
|
||||||
|
#############
|
||||||
|
settings.title=Podešavanja
|
||||||
|
settings.update=Dostupno ažuriranje
|
||||||
|
settings.appVersion=Verzija aplikacije:
|
||||||
|
settings.downloadOption.title=Odaberite opciju preuzimanja (Za preuzimanje pojedinačnih fajlova bez zip formata):
|
||||||
|
settings.downloadOption.1=Otvori u istom prozoru
|
||||||
|
settings.downloadOption.2=Otvori u novom prozoru
|
||||||
|
settings.downloadOption.3=Preuzmi fajl
|
||||||
|
settings.zipThreshold=Zipuj fajlove kada pređe broj preuzetih fajlova
|
||||||
|
settings.signOut=Odjava
|
||||||
|
settings.accountSettings=Podešavanja naloga
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
changeCreds.title=Promeni pristupne podatke
|
||||||
|
changeCreds.header=Ažurirajte detalje svog naloga
|
||||||
|
changeCreds.changeUserAndPassword=Koristite podrazumevane prijavne podatke. Unesite novu lozinku (i korisničko ime ako želite)
|
||||||
|
changeCreds.newUsername=Novo korisničko ime
|
||||||
|
changeCreds.oldPassword=Trenutna lozinka
|
||||||
|
changeCreds.newPassword=Nova lozinka
|
||||||
|
changeCreds.confirmNewPassword=Potvrdite novu lozinku
|
||||||
|
changeCreds.submit=Potvrdi promene
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
account.title=Podešavanja naloga
|
||||||
|
account.accountSettings=Podešavanja naloga
|
||||||
|
account.adminSettings=Admin podešavanja - Pregled i dodavanje korisnika
|
||||||
|
account.userControlSettings=Podešavanja kontrole korisnika
|
||||||
|
account.changeUsername=Pormeni korisničko ime
|
||||||
|
account.changeUsername=Pormeni korisničko ime
|
||||||
|
account.password=Potvrda lozinke
|
||||||
|
account.oldPassword=Stara lozinka
|
||||||
|
account.newPassword=Nova lozinka
|
||||||
|
account.changePassword=Pormeni lozinku
|
||||||
|
account.confirmNewPassword=Potvrdi novu lozinku
|
||||||
|
account.signOut=Odjava
|
||||||
|
account.yourApiKey=Tvoj API ključ
|
||||||
|
account.syncTitle=Sinhronizacija podešavanja pregledača sa nalogom
|
||||||
|
account.settingsCompare=Upoređivanje podešavanja:
|
||||||
|
account.property=Svojstvo
|
||||||
|
account.webBrowserSettings=Podešavanja veb pregledača
|
||||||
|
account.syncToBrowser=Sinhronizacija naloga -> pregledač
|
||||||
|
account.syncToAccount=Sinhronizacija naloga <- pregledač
|
||||||
|
|
||||||
|
|
||||||
|
adminUserSettings.title=Podešavanja kontrole korisnika
|
||||||
|
adminUserSettings.header=Podešavanja kontrole korisnika za administratora
|
||||||
|
adminUserSettings.admin=Administrator
|
||||||
|
adminUserSettings.user=Korisnik
|
||||||
|
adminUserSettings.addUser=Dodaj novog korisnika
|
||||||
|
adminUserSettings.roles=Uloge
|
||||||
|
adminUserSettings.role=Uloga
|
||||||
|
adminUserSettings.actions=Akcije
|
||||||
|
adminUserSettings.apiUser=Korisnik s ograničenim API pristupom
|
||||||
|
adminUserSettings.webOnlyUser=Korisnik samo za web
|
||||||
|
adminUserSettings.demoUser=Demo korisnik (Bez prilagođenih podešavanja)
|
||||||
|
adminUserSettings.forceChange=Prisili korisnika da promeni korisničko ime/lozinku pri prijavi
|
||||||
|
adminUserSettings.submit=Sačuvaj korisnika
|
||||||
|
|
||||||
|
#############
|
||||||
|
# HOME-PAGE #
|
||||||
|
#############
|
||||||
|
home.desc=Vaš lokalno hostovan jedinstveni alat za sve vaše potrebe vezane za PDF.
|
||||||
|
home.searchBar=Pretraži funkcije...
|
||||||
|
|
||||||
|
|
||||||
|
home.viewPdf.title=Pregledaj PDF
|
||||||
|
home.viewPdf.desc=Pregledaj, anotiraj, dodaj tekst ili slike
|
||||||
|
viewPdf.tags=pregled,čitanje,anotiranje,tekst,slika
|
||||||
|
|
||||||
|
home.multiTool.title=PDF Multi Alat
|
||||||
|
home.multiTool.desc=Spajanje, rotacija, premeštanje i uklanjanje stranica
|
||||||
|
multiTool.tags=Multi Alat,Multi operacija,Korisnički interfejs,klik i povuci,front end,klijentska strana,interaktivno,pomera
|
||||||
|
|
||||||
|
home.merge.title=Spajanje
|
||||||
|
home.merge.desc=Lako spojite više PDF-ova u jedan.
|
||||||
|
merge.tags=spajanje,Operacije sa stranicama,Backend,server strana
|
||||||
|
|
||||||
|
home.split.title=Razdvajanje
|
||||||
|
home.split.desc=Razdvojite PDF-ove u više dokumenata
|
||||||
|
split.tags=Operacije sa stranicama,podela,Višestruke stranice,sečenje,server strana
|
||||||
|
|
||||||
|
home.rotate.title=Rotacija
|
||||||
|
home.rotate.desc=Lako rotirajte vaše PDF-ove.
|
||||||
|
rotate.tags=server strana
|
||||||
|
|
||||||
|
|
||||||
|
home.imageToPdf.title=Slika u PDF
|
||||||
|
home.imageToPdf.desc=Konvertujte sliku (PNG, JPEG, GIF) u PDF.
|
||||||
|
imageToPdf.tags=konverzija,img,jpg,slika,foto
|
||||||
|
|
||||||
|
home.pdfToImage.title=PDF u Sliku
|
||||||
|
home.pdfToImage.desc=Konvertujte PDF u sliku. (PNG, JPEG, GIF)
|
||||||
|
pdfToImage.tags=konverzija,img,jpg,slika,foto
|
||||||
|
|
||||||
|
home.pdfOrganiser.title=Organizacija
|
||||||
|
home.pdfOrganiser.desc=Uklonite/Premeštajte stranice u bilo kom redosledu
|
||||||
|
pdfOrganiser.tags=duplex,even,odd,sort,move
|
||||||
|
|
||||||
|
|
||||||
|
home.addImage.title=Dodaj sliku
|
||||||
|
home.addImage.desc=Dodaje sliku na određeno mesto u PDF-u
|
||||||
|
addImage.tags=img,jpg,slika,foto
|
||||||
|
|
||||||
|
home.watermark.title=Dodaj vodeni žig
|
||||||
|
home.watermark.desc=Dodajte prilagođeni vodeni žig na vaš PDF dokument.
|
||||||
|
watermark.tags=Tekst,ponavljanje,etiketa,vlastiti,autorsko pravo,zaštita, img,jpg,slika,foto
|
||||||
|
|
||||||
|
home.permissions.title=Promeni dozvole
|
||||||
|
home.permissions.desc=Promenite dozvole vašeg PDF dokumenta
|
||||||
|
permissions.tags=čitanje,pisanje,izmena,štampa
|
||||||
|
|
||||||
|
|
||||||
|
home.removePages.title=Ukloni
|
||||||
|
home.removePages.desc=Obrišite nepotrebne stranice iz vašeg PDF dokumenta.
|
||||||
|
removePages.tags=Ukloni stranice,obriši stranice
|
||||||
|
|
||||||
|
home.addPassword.title=Dodaj lozinku
|
||||||
|
home.addPassword.desc=Enkriptujte vaš PDF dokument lozinkom.
|
||||||
|
addPassword.tags=bezbedno,zaštita
|
||||||
|
|
||||||
|
home.removePassword.title=Ukloni lozinku
|
||||||
|
home.removePassword.desc=Uklonite zaštitu lozinkom sa vašeg PDF dokumenta.
|
||||||
|
removePassword.tags=bezbedno,Dešifruj,zaštita,ukloni lozinku
|
||||||
|
|
||||||
|
home.compressPdfs.title=Kompresuj
|
||||||
|
home.compressPdfs.desc=Kompresujte PDF-ove kako bi smanjili veličinu fajla.
|
||||||
|
compressPdfs.tags=smanji,mali,minijaturni
|
||||||
|
|
||||||
|
|
||||||
|
home.changeMetadata.title=Promena metapodataka
|
||||||
|
home.changeMetadata.desc=Promenite/Uklonite/Dodajte metapodatke u PDF dokumentu
|
||||||
|
changeMetadata.tags=Naslov,autor,datum,kreacije,vreme,izdavač,proizvođač,statistike
|
||||||
|
|
||||||
|
home.fileToPDF.title=Konvertuj fajl u PDF
|
||||||
|
home.fileToPDF.desc=Konvertujte gotovo bilo koji fajl u PDF (DOCX, PNG, XLS, PPT, TXT i više)
|
||||||
|
fileToPDF.tags=transformacija,format,dokument,slika,slajd,tekst,konverzija,office,docs,word,excel,powerpoint
|
||||||
|
|
||||||
|
home.ocr.title=OCR / Čišćenje skenova
|
||||||
|
home.ocr.desc=Čišćenje skenova i detektovanje teksta sa slika unutar PDF-a i ponovno dodavanje kao teksta.
|
||||||
|
ocr.tags=prepoznavanje,tekst,slika,sken,čitanje,identifikacija,detekcija,uređivanje
|
||||||
|
|
||||||
|
|
||||||
|
home.extractImages.title=Izvuci slike
|
||||||
|
home.extractImages.desc=Izvlači sve slike iz PDF-a i čuva ih u zip formatu
|
||||||
|
extractImages.tags=slika,foto,sačuvaj,arhiva,zip,zahvati,uhvati
|
||||||
|
|
||||||
|
home.pdfToPDFA.title=PDF u PDF/A
|
||||||
|
home.pdfToPDFA.desc=Konvertujte PDF u PDF/A za dugoročno čuvanje
|
||||||
|
pdfToPDFA.tags=arhiva,dugoročno,standard,konverzija,čuvanje,čuvanje
|
||||||
|
|
||||||
|
home.PDFToWord.title=PDF u Word
|
||||||
|
home.PDFToWord.desc=Konvertujte PDF u Word formate (DOC, DOCX i ODT)
|
||||||
|
PDFToWord.tags=doc,docx,odt,word,transformacija,format,konverzija,office,microsoft,docfile
|
||||||
|
|
||||||
|
home.PDFToPresentation.title=PDF u Prezentaciju
|
||||||
|
home.PDFToPresentation.desc=Konvertujte PDF u formate za prezentaciju (PPT, PPTX i ODP)
|
||||||
|
PDFToPresentation.tags=slajdovi,prikaz,office,microsoft
|
||||||
|
|
||||||
|
home.PDFToText.title=PDF u RTF (Tekst)
|
||||||
|
home.PDFToText.desc=Konvertujte PDF u tekst ili RTF format
|
||||||
|
PDFToText.tags=richformat,richtextformat,rich text format
|
||||||
|
|
||||||
|
home.PDFToHTML.title=PDF u HTML
|
||||||
|
home.PDFToHTML.desc=Konvertujte PDF u HTML format
|
||||||
|
PDFToHTML.tags=web sadržaj,prijateljski za pretraživače
|
||||||
|
|
||||||
|
|
||||||
|
home.PDFToXML.title=PDF u XML
|
||||||
|
home.PDFToXML.desc=Konvertujte PDF u XML format
|
||||||
|
PDFToXML.tags=izdvajanje-podataka,strukturirani-sadržaj,interop,transformacija,konvertovanje
|
||||||
|
|
||||||
|
home.ScannerImageSplit.title=Detekcija/Razdvajanje skeniranih fotografija
|
||||||
|
home.ScannerImageSplit.desc=Razdvaja više fotografija unutar slike/PDF-a
|
||||||
|
ScannerImageSplit.tags=razdvoji,auto-detekcija,skeniranja,višestruke fotografije,organizacija
|
||||||
|
|
||||||
|
home.sign.title=Potpis
|
||||||
|
home.sign.desc=Dodaje potpis u PDF crtežom, tekstom ili slikom
|
||||||
|
sign.tags=autorizacija,inicijali,crtani-potpis,tekstualni-potpis,slikovni-potpis
|
||||||
|
|
||||||
|
home.flatten.title=Ravnanje
|
||||||
|
home.flatten.desc=Uklanja sve interaktivne elemente i forme iz PDF-a
|
||||||
|
flatten.tags=statično,deaktivirati,neinteraktivno,usmeriti
|
||||||
|
|
||||||
|
home.repair.title=Popravi
|
||||||
|
home.repair.desc=Pokušava popraviti oštećeni/izgubljeni PDF
|
||||||
|
repair.tags=popravi,vrati,korekcija,obnovi
|
||||||
|
|
||||||
|
home.removeBlanks.title=Ukloni prazne stranice
|
||||||
|
home.removeBlanks.desc=Detektuje i uklanja prazne stranice iz dokumenta
|
||||||
|
removeBlanks.tags=čišćenje,usmeriti,ne-sadržaj,organizacija
|
||||||
|
|
||||||
|
home.removeAnnotations.title=Ukloni Anotacije
|
||||||
|
home.removeAnnotations.desc=Uklanja sve komentare/anotacije iz PDF-a
|
||||||
|
removeAnnotations.tags=komentari,isticanje,beleške,oznake,ukloni
|
||||||
|
|
||||||
|
home.compare.title=Uporedi
|
||||||
|
home.compare.desc=Upoređuje i prikazuje razlike između 2 PDF dokumenata
|
||||||
|
compare.tags=razlikovati,kontrast,izmene,analiza
|
||||||
|
|
||||||
|
home.certSign.title=Potpis sa sertifikatom
|
||||||
|
home.certSign.desc=Potpisuje PDF sa sertifikatom/ključem (PEM/P12)
|
||||||
|
certSign.tags=autentifikacija,PEM,P12,zvanično,šifrovanje
|
||||||
|
|
||||||
|
home.pageLayout.title=Višestruki prikaz stranica
|
||||||
|
home.pageLayout.desc=Spaja više stranica PDF dokumenta u jednu stranicu
|
||||||
|
pageLayout.tags=spajanje,kompozit,pojedinačan-prikaz,organizacija
|
||||||
|
|
||||||
|
home.scalePages.title=Podesi veličinu/skalu stranice
|
||||||
|
home.scalePages.desc=Podesi veličinu/skalu stranice i/ili njenog sadržaja.
|
||||||
|
scalePages.tags=izmena,modifikacija,dimenzija,adaptacija
|
||||||
|
|
||||||
|
home.pipeline.title=Pipeline (Napredno)
|
||||||
|
home.pipeline.desc=Pokreće više akcija na PDF-ovima definisanjem skripti u pipelinu
|
||||||
|
pipeline.tags=automatizacija,sekvenciranje,skriptirano,batch-process
|
||||||
|
|
||||||
|
home.add-page-numbers.title=Dodaj brojeve stranica
|
||||||
|
home.add-page-numbers.desc=Dodaje brojeve stranica u dokumentu na određeno mesto
|
||||||
|
add-page-numbers.tags=paginacija,oznaka,organizacija,indeks
|
||||||
|
|
||||||
|
home.auto-rename.title=Automatsko preimenovanje PDF fajla
|
||||||
|
home.auto-rename.desc=Automatski menja ime PDF fajla na osnovu detektovanog zaglavlja
|
||||||
|
auto-rename.tags=auto-detekcija,zaglavlje-bazirano,organizacija,preimenovanje
|
||||||
|
|
||||||
|
home.adjust-contrast.title=Podesi boje/kontrast
|
||||||
|
home.adjust-contrast.desc=Podesi kontrast, zasićenost i osvetljenost PDF-a
|
||||||
|
adjust-contrast.tags=korekcija-boja,podešavanje,modifikacija,unapredi
|
||||||
|
|
||||||
|
home.crop.title=Skraćivanje PDF-a
|
||||||
|
home.crop.desc=Skraćuje PDF radi smanjenja veličine (zadržava tekst!)
|
||||||
|
crop.tags=trimovanje,skupljanje,uređivanje,oblikovanje
|
||||||
|
|
||||||
|
home.autoSplitPDF.title=Automatsko razdvajanje stranica
|
||||||
|
home.autoSplitPDF.desc=Automatski deli skenirane PDF-ove pomoću fizičkog skenera QR koda
|
||||||
|
autoSplitPDF.tags=QR-bazirano,razdvoji,segment-skeniranja,organizacija
|
||||||
|
|
||||||
|
home.sanitizePdf.title=Sanitizacija
|
||||||
|
home.sanitizePdf.desc=Uklanja skripte i druge elemente iz PDF fajlova
|
||||||
|
sanitizePdf.tags=čišćenje,bezbednost,bezbedno,ukloni-pretnje
|
||||||
|
|
||||||
|
home.URLToPDF.title=URL/Website u PDF
|
||||||
|
home.URLToPDF.desc=Konvertuje bilo koji http(s) URL u PDF
|
||||||
|
URLToPDF.tags=uhvati-web,sačuvaj-stranicu,web-u-doc,arhiva
|
||||||
|
|
||||||
|
home.HTMLToPDF.title=HTML u PDF
|
||||||
|
home.HTMLToPDF.desc=Konvertuje bilo koji HTML fajl ili zip u PDF
|
||||||
|
HTMLToPDF.tags=oznake,web-sadržaj,transformacija,konvertovanje
|
||||||
|
|
||||||
|
|
||||||
|
home.MarkdownToPDF.title=Markdown u PDF
|
||||||
|
home.MarkdownToPDF.desc=Konvertuje bilo koji Markdown fajl u PDF
|
||||||
|
MarkdownToPDF.tags=oznake,web-sadržaj,transformacija,konvertovanje
|
||||||
|
|
||||||
|
|
||||||
|
home.getPdfInfo.title=Dohvati SVE informacije o PDF-u
|
||||||
|
home.getPdfInfo.desc=Dobavlja sve moguće informacije o PDF-ovima
|
||||||
|
getPdfInfo.tags=informacije,podaci,statistike
|
||||||
|
|
||||||
|
|
||||||
|
home.extractPage.title=Izdvajanje stranica
|
||||||
|
home.extractPage.desc=Izdvaja odabrane stranice iz PDF-a
|
||||||
|
extractPage.tags=izdvajanje
|
||||||
|
|
||||||
|
|
||||||
|
home.PdfToSinglePage.title=PDF u Jednu Veliku Stranicu
|
||||||
|
home.PdfToSinglePage.desc=Spaja sve stranice PDF-a u jednu veliku stranicu
|
||||||
|
PdfToSinglePage.tags=jedna-stranica
|
||||||
|
|
||||||
|
|
||||||
|
home.showJS.title=Prikaži JavaScript
|
||||||
|
home.showJS.desc=Pretražuje i prikazuje bilo koji JavaScript ubačen u PDF
|
||||||
|
showJS.tags=Cenzura,Sakrij,prekrivanje,crna,marker,skriveno
|
||||||
|
|
||||||
|
home.autoRedact.title=Automatsko Cenzurisanje
|
||||||
|
home.autoRedact.desc=Automatsko cenzurisanje teksta u PDF-u na osnovu unetog teksta
|
||||||
|
showJS.tags=Cenzura,Sakrij,prekrivanje,crna,marker,skriveno
|
||||||
|
|
||||||
|
home.tableExtraxt.title=PDF u CSV
|
||||||
|
home.tableExtraxt.desc=Izdvaja tabele iz PDF-a pretvarajući ih u CSV
|
||||||
|
tableExtraxt.tags=CSV,Izdvajanje tabela,izdvajanje,konvertovanje
|
||||||
|
|
||||||
|
|
||||||
|
home.autoSizeSplitPDF.title=Automatsko Deljenje po Veličini/Broju
|
||||||
|
home.autoSizeSplitPDF.desc=Deljenje jednog PDF-a na više dokumenata na osnovu veličine, broja stranica ili broja dokumenata
|
||||||
|
autoSizeSplitPDF.tags=pdf,delenje,dokumenti,organizacija
|
||||||
|
|
||||||
|
|
||||||
|
home.overlay-pdfs.title=Preklapanje PDF-ova
|
||||||
|
home.overlay-pdfs.desc=Preklapa PDF-ove jedan preko drugog
|
||||||
|
overlay-pdfs.tags=Preklapanje
|
||||||
|
|
||||||
|
home.split-by-sections.title=Deljenje PDF-a po Odeljcima
|
||||||
|
home.split-by-sections.desc=Deljenje svake stranice PDF-a na manje horizontalne i vertikalne odeljke
|
||||||
|
split-by-sections.tags=Deljenje odeljaka,Deljenje,Podešavanje
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# #
|
||||||
|
# WEB PAGES #
|
||||||
|
# #
|
||||||
|
###########################
|
||||||
|
#login
|
||||||
|
login.title=Prijavite se
|
||||||
|
login.signin=Prijavite se
|
||||||
|
login.rememberme=Zapamti me
|
||||||
|
login.invalid=Neispravno korisničko ime ili lozinka.
|
||||||
|
login.locked=Vaš nalog je zaključan.
|
||||||
|
login.signinTitle=Molimo vas da se prijavite
|
||||||
|
|
||||||
|
|
||||||
|
#auto-redact
|
||||||
|
autoRedact.title=Auto Cenzura
|
||||||
|
autoRedact.header=Auto Cenzura
|
||||||
|
autoRedact.colorLabel=Boja
|
||||||
|
autoRedact.textsToRedactLabel=Tekst za cenzurisanje (razdvojeni linijama)
|
||||||
|
autoRedact.textsToRedactPlaceholder=npr. \nPoverljivo \nVrhunski Tajno
|
||||||
|
autoRedact.useRegexLabel=Koristi Regex
|
||||||
|
autoRedact.wholeWordSearchLabel=Pretraga celih reči
|
||||||
|
autoRedact.customPaddingLabel=Dodatni prazan prostor
|
||||||
|
autoRedact.convertPDFToImageLabel=Konvertuj PDF u PDF-Image (koristi se za uklanjanje teksta iza okvira)
|
||||||
|
autoRedact.submitButton=Potvrdi
|
||||||
|
|
||||||
|
|
||||||
|
#showJS
|
||||||
|
showJS.title=Prikaži Javascript
|
||||||
|
showJS.header=Prikaži Javascript
|
||||||
|
showJS.downloadJS=Preuzmi Javascript
|
||||||
|
showJS.submit=Prikaži
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToSinglePage
|
||||||
|
pdfToSinglePage.title=PDF u Jednu Stranicu
|
||||||
|
pdfToSinglePage.header=PDF u Jednu Stranicu
|
||||||
|
pdfToSinglePage.submit=Konvertuj u Jednu Stranicu
|
||||||
|
|
||||||
|
|
||||||
|
#pageExtracter
|
||||||
|
pageExtracter.title=Izdvajanje stranica
|
||||||
|
pageExtracter.header=Izdvajanje stranica
|
||||||
|
pageExtracter.submit=Izdvoji
|
||||||
|
|
||||||
|
|
||||||
|
#getPdfInfo
|
||||||
|
getPdfInfo.title=Informacije o PDF-u
|
||||||
|
getPdfInfo.header=Informacije o PDF-u
|
||||||
|
getPdfInfo.submit=Informacije
|
||||||
|
getPdfInfo.downloadJson=Preuzmi JSON
|
||||||
|
|
||||||
|
|
||||||
|
#markdown-to-pdf
|
||||||
|
MarkdownToPDF.title=Markdown u PDF
|
||||||
|
MarkdownToPDF.header=Markdown u PDF
|
||||||
|
MarkdownToPDF.submit=Konvertuj
|
||||||
|
MarkdownToPDF.help=Rad u toku
|
||||||
|
MarkdownToPDF.credit=Koristi WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#url-to-pdf
|
||||||
|
URLToPDF.title=URL u PDF
|
||||||
|
URLToPDF.header=URL u PDF
|
||||||
|
URLToPDF.submit=Konvertuj
|
||||||
|
URLToPDF.credit=Koristi WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
#html-to-pdf
|
||||||
|
HTMLToPDF.title=HTML u PDF
|
||||||
|
HTMLToPDF.header=HTML u PDF
|
||||||
|
HTMLToPDF.help=Prihvata HTML fajlove i ZIP-ove koji sadrže html/css/slike itd. potrebno
|
||||||
|
HTMLToPDF.submit=Konvertuj
|
||||||
|
HTMLToPDF.credit=Koristi WeasyPrint
|
||||||
|
|
||||||
|
|
||||||
|
#sanitizePDF
|
||||||
|
sanitizePDF.title=Sanitizacija PDF-a
|
||||||
|
sanitizePDF.header=Sanitizacija PDF fajla
|
||||||
|
sanitizePDF.selectText.1=Ukloni JavaScript akcije
|
||||||
|
sanitizePDF.selectText.2=Ukloni ugrađene fajlove
|
||||||
|
sanitizePDF.selectText.3=Ukloni metapodatke
|
||||||
|
sanitizePDF.selectText.4=Ukloni linkove
|
||||||
|
sanitizePDF.selectText.5=Ukloni fontove
|
||||||
|
sanitizePDF.submit=Sanitizuj PDF
|
||||||
|
|
||||||
|
|
||||||
|
#addPageNumbers
|
||||||
|
addPageNumbers.title=Dodavanje brojeva stranica
|
||||||
|
addPageNumbers.header=Dodavanje brojeva stranica
|
||||||
|
addPageNumbers.selectText.1=Izaberi PDF fajl:
|
||||||
|
addPageNumbers.selectText.2=Veličina margine
|
||||||
|
addPageNumbers.selectText.3=Pozicija
|
||||||
|
addPageNumbers.selectText.4=Početni broj
|
||||||
|
addPageNumbers.selectText.5=Brojane stranice
|
||||||
|
addPageNumbers.selectText.6=Prilagođeni tekst
|
||||||
|
addPageNumbers.customTextDesc=Prilagođeni tekst
|
||||||
|
addPageNumbers.numberPagesDesc=Koje stranice brojati, podrazumevano 'sve', takođe prihvata 1-5 ili 2,5,9 itd.
|
||||||
|
addPageNumbers.customNumberDesc=Podrazumevano je {n}, takođe prihvata 'Stranica {n} od {ukupno}', 'Tekst-{n}', '{ime_fajla}-{n}'
|
||||||
|
addPageNumbers.submit=Dodaj brojeve stranica
|
||||||
|
|
||||||
|
|
||||||
|
#auto-rename
|
||||||
|
auto-rename.title=Automatsko preimenovanje
|
||||||
|
auto-rename.header=Automatsko preimenovanje PDF-a
|
||||||
|
auto-rename.submit=Automatsko preimenovanje
|
||||||
|
|
||||||
|
|
||||||
|
#adjustContrast
|
||||||
|
adjustContrast.title=Podesi Kontrast
|
||||||
|
adjustContrast.header=Podesi Kontrast
|
||||||
|
adjustContrast.contrast=Kontrast:
|
||||||
|
adjustContrast.brightness=Osvetljenje:
|
||||||
|
adjustContrast.saturation=Zasićenje:
|
||||||
|
adjustContrast.download=Preuzmi
|
||||||
|
|
||||||
|
|
||||||
|
#crop
|
||||||
|
crop.title=Iseci
|
||||||
|
crop.header=Iseci Sliku
|
||||||
|
crop.submit=Potvrdi
|
||||||
|
|
||||||
|
|
||||||
|
#autoSplitPDF
|
||||||
|
autoSplitPDF.title=Automatsko Deljenje PDF-a
|
||||||
|
autoSplitPDF.header=Automatsko Deljenje PDF-a
|
||||||
|
autoSplitPDF.description=Štampajte, umetnite, skenirajte, učitajte i dozvolite nam da automatski razdvojimo vaše dokumente. Nije potrebno ručno sortiranje.
|
||||||
|
autoSplitPDF.selectText.1=Odštampajte nekoliko listova razdeljivača ispod (Crno-belo je u redu).
|
||||||
|
autoSplitPDF.selectText.2=Skenirajte sve vaše dokumente odjednom, ubacivanjem lista razdeljivača između njih.
|
||||||
|
autoSplitPDF.selectText.3=Učitajte jedan veliki skenirani PDF fajl i dozvolite Stirling PDF-u da obavi ostalo.
|
||||||
|
autoSplitPDF.selectText.4=Listovi razdeljivača se automatski detektuju i uklanjaju, obezbeđujući uredan konačni dokument.
|
||||||
|
autoSplitPDF.formPrompt=Potvrdite PDF koji sadrži Stirling-PDF listove razdeljivača:
|
||||||
|
autoSplitPDF.duplexMode=Dupleks režim (skeniranje prednje i zadnje strane)
|
||||||
|
autoSplitPDF.dividerDownload1=Preuzmi 'Auto Splitter Divider (minimal).pdf'
|
||||||
|
autoSplitPDF.dividerDownload2=Preuzmi 'Auto Splitter Divider (sa uputstvima).pdf'
|
||||||
|
autoSplitPDF.submit=Potvrdi
|
||||||
|
|
||||||
|
|
||||||
|
#pipeline
|
||||||
|
pipeline.title=Tok rada
|
||||||
|
|
||||||
|
|
||||||
|
#pageLayout
|
||||||
|
pageLayout.title=Višestruki Raspored Stranica
|
||||||
|
pageLayout.header=Višestruki Raspored Stranica
|
||||||
|
pageLayout.pagesPerSheet=Stranica po listu:
|
||||||
|
pageLayout.addBorder=Dodaj ivice
|
||||||
|
pageLayout.submit=Potvrdi
|
||||||
|
|
||||||
|
|
||||||
|
#scalePages
|
||||||
|
scalePages.title=Podesi razmeru stranica
|
||||||
|
scalePages.header=Podesi razmeru stranica
|
||||||
|
scalePages.pageSize=Veličina stranice dokumenta.
|
||||||
|
scalePages.scaleFactor=Nivo zumiranja (rezanje) stranice.
|
||||||
|
scalePages.submit=Potvrdi
|
||||||
|
|
||||||
|
|
||||||
|
#certSign
|
||||||
|
certSign.title=Potpisivanje Sertifikatom
|
||||||
|
certSign.header=Potpiši PDF sa svojim sertifikatom (Rad u toku)
|
||||||
|
certSign.selectPDF=Izaberite PDF fajl za potpisivanje:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
|
certSign.selectKey=Izaberite svoj privatni ključ (PKCS#8 format, može biti .pem ili .der):
|
||||||
|
certSign.selectCert=Izaberite svoj sertifikat (X.509 format, može biti .pem ili .der):
|
||||||
|
certSign.selectP12=Izaberite svoj PKCS#12 keystore fajl (.p12 ili .pfx) (Opciono, ako je dostupan, trebalo bi da sadrži vaš privatni ključ i sertifikat):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
|
certSign.certType=Tip sertifikata
|
||||||
|
certSign.password=Unesite lozinku vašeg keystore-a ili privatnog ključa (ako je ima):
|
||||||
|
certSign.showSig=Prikaži potpis
|
||||||
|
certSign.reason=Razlog
|
||||||
|
certSign.location=Lokacija
|
||||||
|
certSign.name=Ime
|
||||||
|
certSign.submit=Potpiši PDF
|
||||||
|
|
||||||
|
|
||||||
|
#removeBlanks
|
||||||
|
removeBlanks.title=Ukloni prazne stranice
|
||||||
|
removeBlanks.header=Ukloni prazne stranice
|
||||||
|
removeBlanks.threshold=Prag beline piksela:
|
||||||
|
removeBlanks.thresholdDesc=Prag za određivanje koliko beli piksel mora biti 'beli'. 0 = Crno, 255 čisto belo.
|
||||||
|
removeBlanks.whitePercent=Procenat bele boje (%):
|
||||||
|
removeBlanks.whitePercentDesc=Procenat stranice koji mora biti 'beli' pikseli da bi se uklonili
|
||||||
|
removeBlanks.submit=Ukloni prazne
|
||||||
|
|
||||||
|
|
||||||
|
#removeAnnotations
|
||||||
|
removeAnnotations.title=Ukloni Anotacije
|
||||||
|
removeAnnotations.header=Ukloni Anotacije
|
||||||
|
removeAnnotations.submit=Ukloni
|
||||||
|
|
||||||
|
|
||||||
|
#compare
|
||||||
|
compare.title=Uporedi
|
||||||
|
compare.header=Uporedi PDF fajlove
|
||||||
|
compare.document.1=Dokument 1
|
||||||
|
compare.document.2=Dokument 2
|
||||||
|
compare.submit=Uporedi
|
||||||
|
|
||||||
|
|
||||||
|
#sign
|
||||||
|
sign.title=Potpiši
|
||||||
|
sign.header=Potpiši PDF fajlove
|
||||||
|
sign.upload=Učitaj sliku
|
||||||
|
sign.draw=Nacrtaj potpis
|
||||||
|
sign.text=Tekstualni unos
|
||||||
|
sign.clear=Obriši
|
||||||
|
sign.add=Dodaj
|
||||||
|
|
||||||
|
|
||||||
|
#repair
|
||||||
|
repair.title=Popravi
|
||||||
|
repair.header=Popravi PDF fajlove
|
||||||
|
repair.submit=Popravi
|
||||||
|
|
||||||
|
|
||||||
|
#flatten
|
||||||
|
flatten.title=Ravnanje
|
||||||
|
flatten.header=Ravnanje PDF fajlova
|
||||||
|
flatten.submit=Ravnanje
|
||||||
|
|
||||||
|
|
||||||
|
#ScannerImageSplit
|
||||||
|
ScannerImageSplit.selectText.1=Ugao praga:
|
||||||
|
ScannerImageSplit.selectText.2=Postavlja minimalni apsolutni ugao potreban za rotiranje slike (podrazumevano: 10).
|
||||||
|
ScannerImageSplit.selectText.3=Tolerancija:
|
||||||
|
ScannerImageSplit.selectText.4=Određuje opseg varijacije boja oko procenjene boje pozadine (podrazumevano: 30).
|
||||||
|
ScannerImageSplit.selectText.5=Minimalna površina:
|
||||||
|
ScannerImageSplit.selectText.6=Postavlja minimalni prag površine za fotografiju (podrazumevano: 10000).
|
||||||
|
ScannerImageSplit.selectText.7=Minimalna površina konture:
|
||||||
|
ScannerImageSplit.selectText.8=Postavlja minimalni prag površine konture za fotografiju
|
||||||
|
ScannerImageSplit.selectText.9=Veličina ivice:
|
||||||
|
ScannerImageSplit.selectText.10=Postavlja veličinu ivice dodate i uklonjene kako bi se sprečile bele ivice u izlazu (podrazumevano: 1).
|
||||||
|
|
||||||
|
|
||||||
|
#OCR
|
||||||
|
ocr.title=OCR / Čišćenje skeniranja
|
||||||
|
ocr.header=Čišćenje skeniranja / OCR (Optičko prepoznavanje znakova)
|
||||||
|
ocr.selectText.1=Odaberite jezike koji će biti detektovani unutar PDF-a (Navedeni su trenutno detektovani):
|
||||||
|
ocr.selectText.2=Proizvedi tekstualni fajl koji sadrži OCR tekst uz OCR-ovani PDF
|
||||||
|
ocr.selectText.3=Ispravite stranice koje su skenirane pod uglom rotirajući ih na svoje mesto
|
||||||
|
ocr.selectText.4=Očistite stranicu tako da je manje verovatno da će OCR pronaći tekst u pozadinskom šumu. (Bez promene izlaza)
|
||||||
|
ocr.selectText.5=Očistite stranicu tako da je manje verovatno da će OCR pronaći tekst u pozadinskom šumu, zadržavajući čišćenje u izlazu.
|
||||||
|
ocr.selectText.6=Ignoriše stranice koje imaju interaktivni tekst, samo OCR-uje stranice koje su slike
|
||||||
|
ocr.selectText.7=Prinudni OCR, OCR-uje svaku stranicu uklanjajući sve originalne tekstualne elemente
|
||||||
|
ocr.selectText.8=Normalno (Prikaže grešku ako PDF sadrži tekst)
|
||||||
|
ocr.selectText.9=Dodatne postavke
|
||||||
|
ocr.selectText.10=Režim OCR-a
|
||||||
|
ocr.selectText.11=Ukloni slike nakon OCR-a (Uklanja SVE slike, korisno samo ako je deo koraka konverzije)
|
||||||
|
ocr.selectText.12=Tip rendiranja (Napredno)
|
||||||
|
ocr.help=Molimo vas da pročitate ovu dokumentaciju o tome kako koristiti ovo za druge jezike i/ili korišćenje van docker-a
|
||||||
|
ocr.credit=Ova usluga koristi OCRmyPDF i Tesseract za OCR.
|
||||||
|
ocr.submit=Obradi PDF sa OCR-om
|
||||||
|
|
||||||
|
|
||||||
|
#extractImages
|
||||||
|
extractImages.title=Izdvajanje slika
|
||||||
|
extractImages.header=Izdvajanje slika
|
||||||
|
extractImages.selectText=Odaberite format slike za konvertovanje izdvojenih slika
|
||||||
|
extractImages.submit=Izdvajanje
|
||||||
|
|
||||||
|
|
||||||
|
#File to PDF
|
||||||
|
fileToPDF.title=Fajl u PDF
|
||||||
|
fileToPDF.header=Konvertuj bilo koji fajl u PDF
|
||||||
|
fileToPDF.credit=Ova usluga koristi LibreOffice i Unoconv za konverziju fajla.
|
||||||
|
fileToPDF.supportedFileTypes=Podržani tipovi fajlova bi trebali uključivati navedeno, ali za punu ažuriranu listu podržanih formata, molimo pogledajte LibreOffice dokumentaciju
|
||||||
|
fileToPDF.submit=Konvertuj u PDF
|
||||||
|
|
||||||
|
|
||||||
|
#compress
|
||||||
|
compress.title=Kompresija
|
||||||
|
compress.header=Kompresuj PDF
|
||||||
|
compress.credit=Ova usluga koristi Ghostscript za kompresiju / optimizaciju PDF-a.
|
||||||
|
compress.selectText.1=Ručni režim - Od 1 do 4
|
||||||
|
compress.selectText.2=Nivo optimizacije:
|
||||||
|
compress.selectText.3=4 (Užasno za tekstualne slike)
|
||||||
|
compress.selectText.4=Automatski režim - Automatski prilagođava kvalitet kako bi PDF bio tačne veličine
|
||||||
|
compress.selectText.5=Očekivana veličina PDF-a (npr. 25MB, 10.8MB, 25KB)
|
||||||
|
compress.submit=Kompresuj
|
||||||
|
|
||||||
|
|
||||||
|
#Add image
|
||||||
|
addImage.title=Dodaj sliku
|
||||||
|
addImage.header=Dodaj sliku u PDF
|
||||||
|
addImage.everyPage=Na svakoj stranici?
|
||||||
|
addImage.upload=Dodaj sliku
|
||||||
|
addImage.submit=Dodaj sliku
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
merge.title=Spajanje
|
||||||
|
merge.header=Spajanje više PDF fajlova (2+)
|
||||||
|
merge.sortByName=Sortiraj po imenu
|
||||||
|
merge.sortByDate=Sortiraj po datumu
|
||||||
|
merge.submit=Spajanje
|
||||||
|
|
||||||
|
|
||||||
|
#pdfOrganiser
|
||||||
|
pdfOrganiser.title=Organizator stranica
|
||||||
|
pdfOrganiser.header=Organizator stranica u PDF-u
|
||||||
|
pdfOrganiser.submit=Preuredi stranice
|
||||||
|
|
||||||
|
|
||||||
|
#multiTool
|
||||||
|
multiTool.title=PDF Multi Alatka
|
||||||
|
multiTool.header=PDF Multi Alatka
|
||||||
|
|
||||||
|
#view pdf
|
||||||
|
viewPdf.title=Prikaz
|
||||||
|
viewPdf.header=Prikaz PDF-a
|
||||||
|
|
||||||
|
#pageRemover
|
||||||
|
pageRemover.title=Uklanjanje stranica
|
||||||
|
pageRemover.header=Uklanjanje stranica iz PDF-a
|
||||||
|
pageRemover.pagesToDelete=Stranice za brisanje (Unesite listu brojeva stranica odvojenih zarezima) :
|
||||||
|
pageRemover.submit=Obriši stranice
|
||||||
|
|
||||||
|
|
||||||
|
#rotate
|
||||||
|
rotate.title=Rotiranje PDF-a
|
||||||
|
rotate.header=Rotiranje PDF-a
|
||||||
|
rotate.selectAngle=Izaberite ugao rotacije (u višestrukim od 90 stepeni):
|
||||||
|
rotate.submit=Rotiraj
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
split.title=Razdvajanje PDF-a
|
||||||
|
split.header=Razdvajanje PDF-a
|
||||||
|
split.desc.1=Brojevi koje izaberete predstavljaju brojeve stranica na kojima želite napraviti razdvajanje
|
||||||
|
split.desc.2=Na primer, izbor 1,3,7-8 bi razdvojio dokument od 10 stranica u 6 odvojenih PDF-a sa:
|
||||||
|
split.desc.3=Dokument #1: Stranica 1
|
||||||
|
split.desc.4=Dokument #2: Stranice 2 i 3
|
||||||
|
split.desc.5=Dokument #3: Stranice 4, 5 i 6
|
||||||
|
split.desc.6=Dokument #4: Stranica 7
|
||||||
|
split.desc.7=Dokument #5: Stranica 8
|
||||||
|
split.desc.8=Dokument #6: Stranice 9 i 10
|
||||||
|
split.splitPages=Unesite stranice za razdvajanje:
|
||||||
|
split.submit=Razdvoji
|
||||||
|
|
||||||
|
|
||||||
|
#merge
|
||||||
|
imageToPDF.title=Slika u PDF
|
||||||
|
imageToPDF.header=Slika u PDF
|
||||||
|
imageToPDF.submit=Konvertuj
|
||||||
|
imageToPDF.selectLabel=Opcije prilagođavanja slike
|
||||||
|
imageToPDF.fillPage=Popuni stranicu
|
||||||
|
imageToPDF.fitDocumentToImage=Prilagodi stranicu slici
|
||||||
|
imageToPDF.maintainAspectRatio=Očuvaj proporcije
|
||||||
|
imageToPDF.selectText.2=Automatsko rotiranje PDF-a
|
||||||
|
imageToPDF.selectText.3=Logika za više fajlova (Omogućeno samo ako radite sa više slika)
|
||||||
|
imageToPDF.selectText.4=Spoji u jedan PDF
|
||||||
|
imageToPDF.selectText.5=Konvertuj u odvojene PDF-ove
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToImage
|
||||||
|
pdfToImage.title=PDF u sliku
|
||||||
|
pdfToImage.header=PDF u sliku
|
||||||
|
pdfToImage.selectText=Format slike
|
||||||
|
pdfToImage.singleOrMultiple=Tip rezultata slike po stranici
|
||||||
|
pdfToImage.single=Jedna velika slika koja sadrži sve stranice
|
||||||
|
pdfToImage.multi=Više slika, po jedna slika po stranici
|
||||||
|
pdfToImage.colorType=Tip boje
|
||||||
|
pdfToImage.color=Boja
|
||||||
|
pdfToImage.grey=Nijanse sive
|
||||||
|
pdfToImage.blackwhite=Crno-belo (Može izgubiti podatke!)
|
||||||
|
pdfToImage.submit=Konvertuj
|
||||||
|
|
||||||
|
|
||||||
|
#addPassword
|
||||||
|
addPassword.title=Dodaj šifru
|
||||||
|
addPassword.header=Dodaj šifru (Enkripcija)
|
||||||
|
addPassword.selectText.1=Izaberite PDF za enkripciju
|
||||||
|
addPassword.selectText.2=Korisnička šifra
|
||||||
|
addPassword.selectText.3=Dužina enkripcijskog ključa
|
||||||
|
addPassword.selectText.4=Veće vrednosti su jače, ali manje vrednosti imaju bolju kompatibilnost.
|
||||||
|
addPassword.selectText.5=Postavke dozvola (Preporučuje se korišćenje sa šifrom vlasnika)
|
||||||
|
addPassword.selectText.6=Onemogući sastavljanje dokumenta
|
||||||
|
addPassword.selectText.7=Onemogući ekstrakciju sadržaja
|
||||||
|
addPassword.selectText.8=Onemogući ekstrakciju za pristupačnost
|
||||||
|
addPassword.selectText.9=Onemogući popunjavanje formulara
|
||||||
|
addPassword.selectText.10=Onemogući modifikaciju
|
||||||
|
addPassword.selectText.11=Onemogući modifikaciju anotacija
|
||||||
|
addPassword.selectText.12=Onemogući štampanje
|
||||||
|
addPassword.selectText.13=Onemogući štampanje u različitim formatima
|
||||||
|
addPassword.selectText.14=Šifra vlasnika
|
||||||
|
addPassword.selectText.15=Ograničava šta se može raditi sa dokumentom nakon otvaranja (Nije podržano od svih čitača)
|
||||||
|
addPassword.selectText.16=Ograničava otvaranje samog dokumenta
|
||||||
|
addPassword.submit=Enkriptuj
|
||||||
|
|
||||||
|
|
||||||
|
#watermark
|
||||||
|
watermark.title=Dodaj vodeni žig
|
||||||
|
watermark.header=Dodaj vodeni žig
|
||||||
|
watermark.selectText.1=Izaberite PDF za dodavanje vodenog žiga:
|
||||||
|
watermark.selectText.2=Tekst vodenog žiga:
|
||||||
|
watermark.selectText.3=Veličina fonta:
|
||||||
|
watermark.selectText.4=Rotacija (0-360):
|
||||||
|
watermark.selectText.5=Širina razmaka (Razmak između svakog vodenog žiga horizontalno):
|
||||||
|
watermark.selectText.6=Visina razmaka (Razmak između svakog vodenog žiga vertikalno):
|
||||||
|
watermark.selectText.7=Opačitost (0% - 100%):
|
||||||
|
watermark.selectText.8=Tip vodenog žiga:
|
||||||
|
watermark.selectText.9=Slika vodenog žiga:
|
||||||
|
watermark.submit=Dodaj vodeni žig
|
||||||
|
|
||||||
|
|
||||||
|
#Change permissions
|
||||||
|
permissions.title=Promeni dozvole
|
||||||
|
permissions.header=Promeni dozvole
|
||||||
|
permissions.warning=Upozorenje: Da biste ove dozvole učinili nepromenljivim, preporučuje se postavljanje šifre putem stranice za dodavanje šifre.
|
||||||
|
permissions.selectText.1=Izaberite PDF za promenu dozvola
|
||||||
|
permissions.selectText.2=Postavke dozvola
|
||||||
|
permissions.selectText.3=Onemogući sastavljanje dokumenta
|
||||||
|
permissions.selectText.4=Onemogući ekstrakciju sadržaja
|
||||||
|
permissions.selectText.5=Onemogući ekstrakciju za pristupačnost
|
||||||
|
permissions.selectText.6=Onemogući popunjavanje formulara
|
||||||
|
permissions.selectText.7=Onemogući modifikaciju
|
||||||
|
permissions.selectText.8=Onemogući modifikaciju anotacija
|
||||||
|
permissions.selectText.9=Onemogući štampanje
|
||||||
|
permissions.selectText.10=Onemogući štampanje u različitim formatima
|
||||||
|
permissions.submit=Promeni
|
||||||
|
|
||||||
|
|
||||||
|
#remove password
|
||||||
|
removePassword.title=Ukloni šifru
|
||||||
|
removePassword.header=Ukloni šifru (Dekripcija)
|
||||||
|
removePassword.selectText.1=Izaberite PDF za dekripciju
|
||||||
|
removePassword.selectText.2=Šifra
|
||||||
|
removePassword.submit=Ukloni
|
||||||
|
|
||||||
|
|
||||||
|
#changeMetadata
|
||||||
|
changeMetadata.title=Naslov:
|
||||||
|
changeMetadata.header=Promeni metapodatke
|
||||||
|
changeMetadata.selectText.1=Izmenite promenljive koje želite promeniti
|
||||||
|
changeMetadata.selectText.2=Obriši sve metapodatke
|
||||||
|
changeMetadata.selectText.3=Prikaži prilagođene metapodatke:
|
||||||
|
changeMetadata.author=Autor:
|
||||||
|
changeMetadata.creationDate=Datum kreiranja (gggg/MM/dd HH:mm:ss):
|
||||||
|
changeMetadata.creator=Kreator:
|
||||||
|
changeMetadata.keywords=Ključne reči:
|
||||||
|
changeMetadata.modDate=Datum izmene (gggg/MM/dd HH:mm:ss):
|
||||||
|
changeMetadata.producer=Proizvođač:
|
||||||
|
changeMetadata.subject=Tema:
|
||||||
|
changeMetadata.title=Naslov:
|
||||||
|
changeMetadata.trapped=Zaglavljeno:
|
||||||
|
changeMetadata.selectText.4=Drugi metapodaci:
|
||||||
|
changeMetadata.selectText.5=Dodaj prilagođeni unos metapodataka
|
||||||
|
changeMetadata.submit=Promeni
|
||||||
|
|
||||||
|
|
||||||
|
#pdfToPDFA
|
||||||
|
pdfToPDFA.title=PDF u PDF/A
|
||||||
|
pdfToPDFA.header=PDF u PDF/A
|
||||||
|
pdfToPDFA.credit=Ova usluga koristi OCRmyPDF za konverziju u PDF/A format
|
||||||
|
pdfToPDFA.submit=Konvertuj
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToWord
|
||||||
|
PDFToWord.title=PDF u Word
|
||||||
|
PDFToWord.header=PDF u Word
|
||||||
|
PDFToWord.selectText.1=Format izlaznog fajla
|
||||||
|
PDFToWord.credit=Ova usluga koristi LibreOffice za konverziju fajlova.
|
||||||
|
PDFToWord.submit=Konvertuj
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToPresentation
|
||||||
|
PDFToPresentation.title=PDF u Prezentaciju
|
||||||
|
PDFToPresentation.header=PDF u Prezentaciju
|
||||||
|
PDFToPresentation.selectText.1=Format izlaznog fajla
|
||||||
|
PDFToPresentation.credit=Ova usluga koristi LibreOffice za konverziju fajlova.
|
||||||
|
PDFToPresentation.submit=Konvertuj
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToText
|
||||||
|
PDFToText.title=PDF u RTF (Tekst)
|
||||||
|
PDFToText.header=PDF u RTF (Tekst)
|
||||||
|
PDFToText.selectText.1=Format izlaznog fajla
|
||||||
|
PDFToText.credit=Ova usluga koristi LibreOffice za konverziju fajlova.
|
||||||
|
PDFToText.submit=Konvertuj
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToHTML
|
||||||
|
PDFToHTML.title=PDF u HTML
|
||||||
|
PDFToHTML.header=PDF u HTML
|
||||||
|
PDFToHTML.credit=Ova usluga koristi LibreOffice za konverziju fajlova.
|
||||||
|
PDFToHTML.submit=Konvertuj
|
||||||
|
|
||||||
|
|
||||||
|
#PDFToXML
|
||||||
|
PDFToXML.title=PDF u XML
|
||||||
|
PDFToXML.header=PDF u XML
|
||||||
|
PDFToXML.credit=Ova usluga koristi LibreOffice za konverziju fajlova.
|
||||||
|
PDFToXML.submit=Konvertuj
|
||||||
|
|
||||||
|
#PDFToCSV
|
||||||
|
PDFToCSV.title=PDF u CSV
|
||||||
|
PDFToCSV.header=PDF u CSV
|
||||||
|
PDFToCSV.prompt=Izaberite stranicu za ekstrakciju tabele
|
||||||
|
PDFToCSV.submit=Izvuci
|
||||||
|
|
||||||
|
#split-by-size-or-count
|
||||||
|
split-by-size-or-count.header=Razdvoji PDF po veličini ili broju
|
||||||
|
split-by-size-or-count.type.label=Izaberite tip razdvajanja
|
||||||
|
split-by-size-or-count.type.size=Po veličini
|
||||||
|
split-by-size-or-count.type.pageCount=Po broju stranica
|
||||||
|
split-by-size-or-count.type.docCount=Po broju dokumenata
|
||||||
|
split-by-size-or-count.value.label=Unesite vrednost
|
||||||
|
split-by-size-or-count.value.placeholder=Unesite veličinu (npr. 2MB ili 3KB) ili broj (npr. 5)
|
||||||
|
split-by-size-or-count.submit=Potvrdi
|
||||||
|
|
||||||
|
|
||||||
|
#overlay-pdfs
|
||||||
|
overlay-pdfs.header=Preklapanje PDF fajlova
|
||||||
|
overlay-pdfs.baseFile.label=Izaberite osnovni PDF fajl
|
||||||
|
overlay-pdfs.overlayFiles.label=Izaberite PDF fajlove za preklapanje
|
||||||
|
overlay-pdfs.mode.label=Izaberite režim preklapanja
|
||||||
|
overlay-pdfs.mode.sequential=Sekvencijalno preklapanje
|
||||||
|
overlay-pdfs.mode.interleaved=Interleaved preklapanje
|
||||||
|
overlay-pdfs.mode.fixedRepeat=Fixed Repeat preklapanje
|
||||||
|
overlay-pdfs.counts.label=Broj preklapanja (za režim Fixed Repeat)
|
||||||
|
overlay-pdfs.counts.placeholder=Unesite brojeve odvojene zarezom (npr. 2,3,1)
|
||||||
|
overlay-pdfs.position.label=Izaberite poziciju preklapanja
|
||||||
|
overlay-pdfs.position.foreground=Prethodni plan
|
||||||
|
overlay-pdfs.position.background=Pozadina
|
||||||
|
overlay-pdfs.submit=Potvrdi
|
||||||
|
|
||||||
|
|
||||||
|
#split-by-sections
|
||||||
|
split-by-sections.title=Razdvoji PDF po sekcijama
|
||||||
|
split-by-sections.header=Razdvoji PDF u sekcije
|
||||||
|
split-by-sections.horizontal.label=Horizontalne podele
|
||||||
|
split-by-sections.vertical.label=Vertikalne podele
|
||||||
|
split-by-sections.horizontal.placeholder=Unesite broj horizontalnih podele
|
||||||
|
split-by-sections.vertical.placeholder=Unesite broj vertikalnih podele
|
||||||
|
split-by-sections.submit=Razdvoji PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Red
|
|||||||
green=Green
|
green=Green
|
||||||
blue=Blue
|
blue=Blue
|
||||||
custom=Custom...
|
custom=Custom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Credentials changed!
|
changedCredsMessage=Credentials changed!
|
||||||
notAuthenticatedMessage=User not authenticated.
|
notAuthenticatedMessage=User not authenticated.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Current password is incorrect.
|
|||||||
usernameExistsMessage=New Username already exists.
|
usernameExistsMessage=New Username already exists.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Submit
|
|||||||
certSign.title=Certifikatsignering
|
certSign.title=Certifikatsignering
|
||||||
certSign.header=Skriv under en PDF med ditt certifikat (Pågående arbete)
|
certSign.header=Skriv under en PDF med ditt certifikat (Pågående arbete)
|
||||||
certSign.selectPDF=Välj en PDF-fil för signering:
|
certSign.selectPDF=Välj en PDF-fil för signering:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Välj din privata nyckelfil (PKCS#8-format, kan vara .pem eller .der):
|
certSign.selectKey=Välj din privata nyckelfil (PKCS#8-format, kan vara .pem eller .der):
|
||||||
certSign.selectCert=Välj din certifikatfil (X.509-format, kan vara .pem eller .der):
|
certSign.selectCert=Välj din certifikatfil (X.509-format, kan vara .pem eller .der):
|
||||||
certSign.selectP12=Välj din PKCS#12-nyckellagringsfil (.p12 eller .pfx) (Valfritt, om den tillhandahålls bör den innehålla din privata nyckel och certifikat):
|
certSign.selectP12=Välj din PKCS#12-nyckellagringsfil (.p12 eller .pfx) (Valfritt, om den tillhandahålls bör den innehålla din privata nyckel och certifikat):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Certifikattyp
|
certSign.certType=Certifikattyp
|
||||||
certSign.password=Ange ditt nyckellager eller privata nyckellösenord (om något):
|
certSign.password=Ange ditt nyckellager eller privata nyckellösenord (om något):
|
||||||
certSign.showSig=Visa signatur
|
certSign.showSig=Visa signatur
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Kırmızı
|
|||||||
green=Yeşil
|
green=Yeşil
|
||||||
blue=Mavi
|
blue=Mavi
|
||||||
custom=Özel
|
custom=Özel
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=Bilgiler değiştirildi!
|
changedCredsMessage=Bilgiler değiştirildi!
|
||||||
notAuthenticatedMessage=Kullanıcı doğrulanmadı.
|
notAuthenticatedMessage=Kullanıcı doğrulanmadı.
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=Mevcut şifre yanlış.
|
|||||||
usernameExistsMessage=Yeni Kullanıcı Adı zaten var.
|
usernameExistsMessage=Yeni Kullanıcı Adı zaten var.
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=Gönder
|
|||||||
certSign.title=Sertifika İmzalama
|
certSign.title=Sertifika İmzalama
|
||||||
certSign.header=Sertifikanızla bir PDF imzalayın (Devam eden iş)
|
certSign.header=Sertifikanızla bir PDF imzalayın (Devam eden iş)
|
||||||
certSign.selectPDF=İmzalamak için bir PDF Dosyası seçin:
|
certSign.selectPDF=İmzalamak için bir PDF Dosyası seçin:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=Özel Anahtar Dosyanızı Seçin (PKCS#8 formatında, .pem veya .der olabilir):
|
certSign.selectKey=Özel Anahtar Dosyanızı Seçin (PKCS#8 formatında, .pem veya .der olabilir):
|
||||||
certSign.selectCert=Sertifika Dosyanızı Seçin (X.509 formatında, .pem veya .der olabilir):
|
certSign.selectCert=Sertifika Dosyanızı Seçin (X.509 formatında, .pem veya .der olabilir):
|
||||||
certSign.selectP12=PKCS#12 Anahtar Deposu Dosyanızı Seçin (.p12 veya .pfx) (İsteğe bağlı, sağlanırsa, özel anahtarınızı ve sertifikanızı içermelidir):
|
certSign.selectP12=PKCS#12 Anahtar Deposu Dosyanızı Seçin (.p12 veya .pfx) (İsteğe bağlı, sağlanırsa, özel anahtarınızı ve sertifikanızı içermelidir):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=Sertifika Türü
|
certSign.certType=Sertifika Türü
|
||||||
certSign.password=Anahtar Deposu veya Özel Anahtar Şifrenizi Girin (Varsa):
|
certSign.password=Anahtar Deposu veya Özel Anahtar Şifrenizi Girin (Varsa):
|
||||||
certSign.showSig=İmzayı Göster
|
certSign.showSig=İmzayı Göster
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=Vertical Divisions
|
|||||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||||
split-by-sections.submit=Split PDF
|
split-by-sections.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
###########
|
###########
|
||||||
# Generic #
|
# Generic #
|
||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
# the direction that the language is written (ltr=left to right, rtl = right to left)
|
||||||
@@ -42,6 +42,8 @@ red=Red
|
|||||||
green=Green
|
green=Green
|
||||||
blue=Blue
|
blue=Blue
|
||||||
custom=Custom...
|
custom=Custom...
|
||||||
|
WorkInProgess=Work in progress, May not work or be buggy, Please report any ploblems!
|
||||||
|
poweredBy=Powered by
|
||||||
|
|
||||||
changedCredsMessage=凭证已更改!
|
changedCredsMessage=凭证已更改!
|
||||||
notAuthenticatedMessage=用户未经过身份验证。
|
notAuthenticatedMessage=用户未经过身份验证。
|
||||||
@@ -50,6 +52,29 @@ incorrectPasswordMessage=当前密码不正确。
|
|||||||
usernameExistsMessage=新用户名已存在。
|
usernameExistsMessage=新用户名已存在。
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
# Pipeline #
|
||||||
|
###############
|
||||||
|
pipeline.header=Pipeline Menu (Alpha)
|
||||||
|
pipeline.uploadButton=Upload Custom
|
||||||
|
pipeline.configureButton=Configure
|
||||||
|
pipeline.defaultOption=Custom
|
||||||
|
pipeline.submitButton=Submit
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Pipeline Options #
|
||||||
|
######################
|
||||||
|
pipelineOptions.header=Pipeline Configuration
|
||||||
|
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||||
|
pipelineOptions.saveSettings=Save Operation Settings
|
||||||
|
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||||
|
pipelineOptions.addOperationButton=Add operation
|
||||||
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
|
pipelineOptions.saveButton=Download
|
||||||
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -522,9 +547,11 @@ scalePages.submit=提交
|
|||||||
certSign.title=证书签名
|
certSign.title=证书签名
|
||||||
certSign.header=使用您的证书签署 PDF(进行中)
|
certSign.header=使用您的证书签署 PDF(进行中)
|
||||||
certSign.selectPDF=选择要签名的 PDF 文件:
|
certSign.selectPDF=选择要签名的 PDF 文件:
|
||||||
|
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||||
certSign.selectKey=选择您的私钥文件(PKCS#8 格式,可以是 .pem 或 .der):
|
certSign.selectKey=选择您的私钥文件(PKCS#8 格式,可以是 .pem 或 .der):
|
||||||
certSign.selectCert=选择您的证书文件(X.509 格式,可以是 .pem 或 .der):
|
certSign.selectCert=选择您的证书文件(X.509 格式,可以是 .pem 或 .der):
|
||||||
certSign.selectP12=选择您的 PKCS#12 密钥库文件(.p12 或 .pfx)(可选,如果提供,它应该包含您的私钥和证书):
|
certSign.selectP12=选择您的 PKCS#12 密钥库文件(.p12 或 .pfx)(可选,如果提供,它应该包含您的私钥和证书):
|
||||||
|
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||||
certSign.certType=证书类型
|
certSign.certType=证书类型
|
||||||
certSign.password=输入您的密钥库或私钥密码(如果有):
|
certSign.password=输入您的密钥库或私钥密码(如果有):
|
||||||
certSign.showSig=显示签名
|
certSign.showSig=显示签名
|
||||||
@@ -894,3 +921,14 @@ split-by-sections.vertical.label=垂直分割
|
|||||||
split-by-sections.horizontal.placeholder=输入水平分割数
|
split-by-sections.horizontal.placeholder=输入水平分割数
|
||||||
split-by-sections.vertical.placeholder=输入垂直分割数
|
split-by-sections.vertical.placeholder=输入垂直分割数
|
||||||
split-by-sections.submit=分割PDF
|
split-by-sections.submit=分割PDF
|
||||||
|
|
||||||
|
|
||||||
|
#licenses
|
||||||
|
licenses.nav=Licenses
|
||||||
|
licenses.title=3rd Party Licenses
|
||||||
|
licenses.header=3rd Party Licenses
|
||||||
|
licenses.module=Module
|
||||||
|
licenses.version=Version
|
||||||
|
licenses.license=License
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user