Compare commits

..

6 Commits

Author SHA1 Message Date
Anthony Stirling
217bba3a9d Merge branch 'main' into add-elements 2023-12-30 20:17:41 +00:00
Anthony Stirling
0e9f52bca2 Merge branch 'main' into add-elements 2023-12-28 23:52:45 +00:00
Anthony Stirling
4f6286845d Api fix 2023-12-28 22:52:53 +00:00
Anthony Stirling
5d611a2fa3 Merge remote-tracking branch 'origin/main' into add-elements 2023-12-27 15:26:10 +00:00
Saud Fatayerji
86984f2142 Add elements first draft 2023-09-28 19:31:43 +03:00
Saud Fatayerji
e998426b3b Add elements demo (WIP) 2023-09-04 17:07:29 -07:00
348 changed files with 28852 additions and 35652 deletions

View File

@@ -1,5 +1,2 @@
# Formatting # Formatting
5f771b785130154ed47952635b7acef371ffe0ec 5f771b785130154ed47952635b7acef371ffe0ec
# Normalize files
55d4fda01b2f39f5b7d7b4fda5214bd7ff0fd5dd

2
.gitattributes vendored
View File

@@ -1,5 +1,3 @@
* 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

View File

@@ -9,7 +9,3 @@ 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"

View File

@@ -1,18 +1,4 @@
# Description # License Agreement for Contributions
By submitting this pull request, I acknowledge and agree that my contributions will be included in Stirling-PDF and that they can be relicensed in the future under MPL 2.0 (Mozilla Public License Version 2.0) license.
Please provide a summary of the changes, including relevant motivation and context.
Closes #(issue_number)
## Checklist:
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings
## Contributor License Agreement
By submitting this pull request, I acknowledge and agree that my contributions will be included in Stirling-PDF and that they can be relicensed in the future under the MPL 2.0 (Mozilla Public License Version 2.0) license.
(This does not change the general open-source nature of Stirling-PDF, simply moving from one license to another license) (This does not change the general open-source nature of Stirling-PDF, simply moving from one license to another license)

View File

@@ -1,48 +0,0 @@
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

View File

@@ -0,0 +1,3 @@
# License Agreement for Contributions
By submitting this pull request, I acknowledge and agree that my contributions will be included in Stirling-PDF and that they can be relicensed in the future under MPL 2.0 (Mozilla Public License Version 2.0) license.
(This does not change the open-source nature of Stirling-PDF, simply moving from one license to another license)

View File

@@ -6,9 +6,6 @@ on:
branches: branches:
- master - master
- main - main
permissions:
contents: read
packages: write
jobs: jobs:
push: push:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -3,9 +3,7 @@ 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

View File

@@ -1,56 +0,0 @@
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

1
CNAME Normal file
View File

@@ -0,0 +1 @@
stirlingtools.com

View File

@@ -1,40 +0,0 @@
# Contributing to Stirling-PDF
Thank you for your interest in contributing to Stirling-PDF! There are many ways to contribute other than writing code. For example, reporting bugs, creating suggestions, and adding or modifying translations.
## Issue Guidelines
Issues can be used to report bugs, request features, or ask questions. If you have a question, you could also ask us in our [Discord](https://discord.gg/FJUSXUSYec).
Before opening an issue, please check to make sure someone hasn't already opened an issue about it.
## Pull Requests
Before you start working on an issue, please comment on (or create) the issue and wait for it to be assigned to you. If someone has already been assigned but didn't have the time to work on it lately, please communicate with them and ask if they're still working on it. This is to avoid multiple people working on the same issue.
Once you have been assigned an issue, you can start working on it. When you are ready to submit your changes, open a pull request.
For a detailed pull request tutorial, see [this guide](https://www.digitalocean.com/community/tutorials/how-to-create-a-pull-request-on-github).
Please make sure your Pull Request adheres to the following guidelines:
- Use the PR template provided.
- Keep your Pull Request title succinct, detailed and to the point.
- Keep commits atomic. One commit should contain one change. If you want to make multiple changes, submit multiple Pull Requests.
- Commits should be clear, concise and easy to understand.
- References to the Issue number in the Pull Request and/or Commit message.
## Translations
If you would like to add or modify a translation, please see [How to add new languages to Stirling-PDF](HowToAddNewLanguage.md). Also, please create a Pull Request so others can use it!
## Fixing Bugs or Adding a New Feature
First, make sure you've read the section [Pull Requests](#pull-requests).
To build from source, please follow this [Guide](LocalRunGuide.md).
If, at any point of time, you have a question, please feel free to ask in the same issue thread or in our [Discord](https://discord.gg/FJUSXUSYec).
## License
By contributing to this project, you agree that your contributions will be licensed under the [GPL 3 License](LICENSE). You also acknowledge and agree that your contributions will be included in Stirling-PDF and that they can be relicensed in the future under the MPL 2.0 (Mozilla Public License Version 2.0) license.

View File

@@ -1,32 +1,5 @@
# Main stage # Use the base image
FROM alpine:3.19.1 FROM frooodle/stirling-pdf-base:version8
# JDK for app
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
apk add --no-cache \
ca-certificates \
tzdata \
tini \
bash \
curl \
openjdk17-jre \
# Doc conversion
libreoffice@testing \
# OCR MY PDF (unpaper for descew and other advanced featues)
ocrmypdf \
tesseract-ocr-data-eng \
# CV
py3-opencv \
# python3/pip
python3 && \
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
# uno unoconv and HTML
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
mv /usr/share/tessdata /usr/share/tessdata-original
ARG VERSION_TAG ARG VERSION_TAG
@@ -39,31 +12,36 @@ ENV DOCKER_ENABLE_SECURITY=false \
# PGID=1000 \ # PGID=1000 \
# UMASK=022 \ # UMASK=022 \
# Copy necessary files
COPY scripts /scripts
COPY pipeline /pipeline
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto
COPY build/libs/*.jar app.jar
# Create user and group # Create user and group
##RUN groupadd -g $PGID stirlingpdfgroup && \ ##RUN groupadd -g $PGID stirlingpdfgroup && \
## useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \ ## useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \
## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME && \ ## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
# Set up necessary directories and permissions # Set up necessary directories and permissions
RUN mkdir -p /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /logs /customFiles /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
##&& \ ##&& \
## chown -R stirlingpdfuser:stirlingpdfgroup /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles && \ ## chown -R stirlingpdfuser:stirlingpdfgroup /scripts /usr/share/fonts/opentype/noto /usr/share/tesseract-ocr /configs /customFiles && \
## chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tesseract-ocr-original && \ ## chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tesseract-ocr-original
# Copy necessary files
COPY ./scripts/* /scripts/
COPY ./pipeline/ /pipeline/
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
COPY build/libs/*.jar app.jar
# Set font cache and permissions # Set font cache and permissions
fc-cache -f -v && \ RUN fc-cache -f -v && chmod +x /scripts/*
chmod +x /scripts/*
##&& \
## chown stirlingpdfuser:stirlingpdfgroup /app.jar && \ ## chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
## chmod +x /scripts/init.sh ## chmod +x /scripts/init.sh
# Expose necessary ports
EXPOSE 8080 EXPOSE 8080
# Set user and run command # Set user and run command
##USER stirlingpdfuser ##USER stirlingpdfuser
ENTRYPOINT ["tini", "--", "/scripts/init.sh"] ENTRYPOINT ["/scripts/init.sh"]
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]

View File

@@ -1,61 +1,65 @@
# use alpine # Build jbig2enc in a separate stage
FROM alpine:3.19.1 FROM bellsoft/liberica-openjdk-debian:17
ARG VERSION_TAG ARG VERSION_TAG
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libreoffice-core \
libreoffice-common \
libreoffice-writer \
libreoffice-calc \
libreoffice-impress \
unoconv && \
rm -rf /var/lib/apt/lists/*
# Set Environment Variables # Set Environment Variables
ENV DOCKER_ENABLE_SECURITY=false \ ENV DOCKER_ENABLE_SECURITY=false \
HOME=/home/stirlingpdfuser \ HOME=/home/stirlingpdfuser \
VERSION_TAG=$VERSION_TAG \ VERSION_TAG=$VERSION_TAG \
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75"
# PUID=1000 \ # PUID=1000 \
# PGID=1000 \ # PGID=1000 \
# UMASK=022 \ # UMASK=022 \
# Copy necessary files
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
COPY pipeline /pipeline
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto
COPY build/libs/*.jar app.jar
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
apk add --no-cache \
ca-certificates \
tzdata \
tini \
bash \
curl \
openjdk17-jre \
# Doc conversion
libreoffice@testing \
# python and pip
python3 && \
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
# uno unoconv and HTML
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
# Create user and group # Create user and group
#RUN groupadd -g $PGID stirlingpdfgroup && \ #RUN groupadd -g $PGID stirlingpdfgroup && \
# useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \ # useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME # mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
# Set up necessary directories and permissions # Set up necessary directories and permissions
mkdir -p /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles /logs /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
# chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/fonts/opentype/noto /configs /customFiles # chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/fonts/opentype/noto /configs /customFiles
# Copy necessary files
COPY ./scripts/download-security-jar.sh /scripts/download-security-jar.sh
COPY ./scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
COPY ./pipeline/ /pipeline/
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
COPY build/libs/*.jar app.jar
# Set font cache and permissions # Set font cache and permissions
fc-cache -f -v && \ RUN fc-cache -f -v && \
chmod +x /scripts/*.sh chmod +x /scripts/init-without-ocr.sh && \
chmod +x /scripts/download-security-jar.sh
# chown stirlingpdfuser:stirlingpdfgroup /app.jar # chown stirlingpdfuser:stirlingpdfgroup /app.jar
# Set environment variables
ENV ENDPOINTS_GROUPS_TO_REMOVE=OpenCV,OCRmyPDF
ENV DOCKER_ENABLE_SECURITY=false
# Expose the application port
EXPOSE 8080 EXPOSE 8080
# Set environment variables
ENV ENDPOINTS_GROUPS_TO_REMOVE=Python,OpenCV,OCRmyPDF
ENV DOCKER_ENABLE_SECURITY=false
# Run the application # Run the application
#USER stirlingpdfuser #USER stirlingpdfuser
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"] ENTRYPOINT ["/scripts/init-without-ocr.sh"]
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]

View File

@@ -1,5 +1,5 @@
# use alpine # Build jbig2enc in a separate stage
FROM alpine:3.19.1 FROM bellsoft/liberica-openjdk-alpine:17
ARG VERSION_TAG ARG VERSION_TAG
@@ -8,44 +8,39 @@ ENV DOCKER_ENABLE_SECURITY=false \
HOME=/home/stirlingpdfuser \ HOME=/home/stirlingpdfuser \
VERSION_TAG=$VERSION_TAG \ VERSION_TAG=$VERSION_TAG \
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75"
# PUID=1000 \ # PUID=1000 \
# PGID=1000 \ # PGID=1000 \
# UMASK=022 \ # UMASK=022 \
# Copy necessary files
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
COPY pipeline /pipeline
COPY build/libs/*.jar app.jar
# Create user and group using Alpine's addgroup and adduser # Create user and group using Alpine's addgroup and adduser
#RUN addgroup -g $PGID stirlingpdfgroup && \ #RUN addgroup -g $PGID stirlingpdfgroup && \
# adduser -u $PUID -G stirlingpdfgroup -s /bin/sh -D stirlingpdfuser && \ # adduser -u $PUID -G stirlingpdfgroup -s /bin/sh -D stirlingpdfuser && \
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME # mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
# Set up necessary directories and permissions # Set up necessary directories and permissions
#RUN mkdir -p /scripts /configs /customFiles && \ #RUN mkdir -p /scripts /configs /customFiles && \
# chown -R stirlingpdfuser:stirlingpdfgroup /scripts /configs /customFiles /logs /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders # chown -R stirlingpdfuser:stirlingpdfgroup /scripts /configs /customFiles /logs /pipeline /pipeline/defaultWebUIConfigs /pipeline/watchedFolders /pipeline/finishedFolders
RUN mkdir /configs /logs /customFiles && \
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles
COPY ./scripts/download-security-jar.sh /scripts/download-security-jar.sh
COPY ./scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
COPY ./pipeline/ /pipeline/
COPY build/libs/*.jar app.jar
# Set font cache and permissions # Set font cache and permissions
#RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar #RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
chmod +x /scripts/*.sh && \
apk add --no-cache \ RUN chmod +x /scripts/init-without-ocr.sh && \
ca-certificates \ chmod +x /scripts/download-security-jar.sh && \
tzdata \ apk add --no-cache curl
tini \
bash \ # Expose the application port
curl \ EXPOSE 8080
openjdk17-jre && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories
# Set environment variables # Set environment variables
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
EXPOSE 8080 ENTRYPOINT ["/scripts/init-without-ocr.sh"]
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
# Run the application # Run the application
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]

50
DockerfileBase Normal file
View File

@@ -0,0 +1,50 @@
# Main stage
FROM ubuntu:latest AS base
# JDK for app
RUN apt-get update && \
apt-get install -y --no-install-recommends \
openjdk-17-jre
# Doc conversion
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libreoffice-core \
libreoffice-common \
libreoffice-writer \
libreoffice-calc \
libreoffice-impress \
python3-uno \
curl \
unoconv
# OCR MY PDF (unpaper for descew and other advanced featues)
RUN apt-get update && apt-get install -y --no-install-recommends software-properties-common gnupg2 && \
add-apt-repository ppa:alex-p/tesseract-ocr5 && apt install -y --no-install-recommends tesseract-ocr && \
apt-get update && \
apt-get install -y --no-install-recommends \
ghostscript \
python3-pip \
ocrmypdf \
unpaper && \
pip install --upgrade pip && \
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
#CV and HTML
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

View File

@@ -1,41 +0,0 @@
## User Guide for Local Directory Scanning and File Processing
### Whilst Pipelines are in alpha...
You must enable this alpha functionality by setting
```yaml
system:
enableAlphaFunctionality: true
```
To true like in the above for your `/config/settings.yml` file, after restarting Stirling-PDF you should see both UI and folder scanning enabled.
### Setting Up Watched Folders:
- Create a folder where you want your files to be monitored. This is your 'watched folder'.
- The default directory for this is `./pipeline/watchedFolders/`
- Place any directories you want to be scanned into this folder, this folder should contain multiple folders each for their own tasks and pipelines.
### Configuring Processing with JSON Files:
- In each directory you want processed (e.g `./pipeline/watchedFolders/officePrinter`), include a JSON configuration file.
- This JSON file should specify how you want the files in the directory to be handled (e.g., what operations to perform on them) which can be made, configured and downloaded from Stirling-PDF Pipeline interface.r
### Automatic Scanning and Processing:
- The system automatically checks the watched folder every minute for new directories and files to process.
- When a directory with a valid JSON configuration file is found, it begins processing the files inside as per the configuration.
### Processing Steps:
- Files in each directory are processed according to the instructions in the JSON file.
- This might involve file conversions, data filtering, renaming files, etc. If the output of a step is a zip, this zip will be automatically unzipped as it passes to next process.
### Results and Output:
- After processing, the results are saved in a specified output location. This could be a different folder or location as defined in the JSON file or the default location `./pipeline/finishedFolders/`.
- Each processed file is named and organized according to the rules set in the JSON configuration.
### Completion and Cleanup:
- Once processing is complete, the original files in the watched folder's directory are removed.
- You can find the processed files in the designated output location.
### Error Handling:
- If there's an error during processing, the system will not delete the original files, allowing you to check and retry if necessary.
### User Interaction:
- As a user, your main tasks are to set up the watched folders, place directories with files for processing, and create the corresponding JSON configuration files.
- The system handles the rest, including scanning, processing, and outputting results.

View File

@@ -1,4 +1,4 @@
<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 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> </p>
@@ -8,15 +8,15 @@ Fork Stirling-PDF and make a new branch out of Main
Then add reference to the language in the navbar by adding a new language entry to the dropdown Then add reference to the language in the navbar by adding a new language entry to the dropdown
https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/templates/fragments/languages.html https://github.com/Frooodle/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/Stirling-Tools/Stirling-PDF/tree/main/src/main/resources/static/images/flags https://github.com/Frooodle/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 isn't represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia If your language isnt represented by a flag just find whichever closely matches it, such as for Arabic i chose Saudi Arabia
For example to add Polish you would add For example to add Polish you would add
```html ```
<a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL"> <a class="dropdown-item lang_dropdown-item" href="" data-language-code="pl_PL">
<img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski <img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski
</a> </a>
@@ -25,14 +25,14 @@ 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/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) [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)
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
Then simply translate all property entries within that file and make a PR into main for others to use! Then simply translate all property entries within that file and make a PR into main for others to use!
If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but won't be able to verify the translations themselves) If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but wont be able to verify the translations themselves)

View File

@@ -2,8 +2,8 @@
This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker. This document provides instructions on how to add additional language packs for the OCR tab in Stirling-PDF, both inside and outside of Docker.
## My OCR used to work and now doesn't! ## My OCR used to work and now doesnt!
The paths have changed for the tessadata locations on new docker images, please use ``/usr/share/tessdata`` (Others should still work for backwards compatability but might not) Please update your tesseract docker volume path version from 4.00 to 5
## How does the OCR Work ## How does the OCR Work
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) which in turn uses tesseract for its text recognition. Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF) which in turn uses tesseract for its text recognition.
@@ -21,7 +21,7 @@ Depending on your requirements, you can choose the appropriate language pack for
### Installing Language Packs ### Installing Language Packs
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need. 1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata` 2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tesseract-ocr/5/tessdata` (Debian) or `/usr/share/tesseract/tessdata` (Fedora)
# DO NOT REMOVE EXISTING ENG.TRAINEDDATA, IT'S REQUIRED. # DO NOT REMOVE EXISTING ENG.TRAINEDDATA, IT'S REQUIRED.
@@ -37,14 +37,14 @@ services:
your_service_name: your_service_name:
image: your_docker_image_name image: your_docker_image_name
volumes: volumes:
- /location/of/trainingData:/usr/share/tessdata - /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata
``` ```
#### Docker run #### Docker run
Add the following to your existing docker run command Add the following to your existing docker run command
```bash ```bash
-v /location/of/trainingData:/usr/share/tessdata -v /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata
``` ```
#### Non-Docker #### Non-Docker

View File

@@ -65,7 +65,7 @@ sudo make install
``` ```
### Step 3: Install Additional Software ### Step 3: Install Additional Software
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for pattern recognition functionality. Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for patern recognition functionality.
Install the following software: Install the following software:
@@ -109,7 +109,7 @@ pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
```bash ```bash
cd ~/.git &&\ cd ~/.git &&\
git clone https://github.com/Stirling-Tools/Stirling-PDF.git &&\ git clone https://github.com/Frooodle/Stirling-PDF.git &&\
cd Stirling-PDF &&\ cd Stirling-PDF &&\
chmod +x ./gradlew &&\ chmod +x ./gradlew &&\
./gradlew build ./gradlew build
@@ -139,7 +139,7 @@ Easiest is to use the langpacks provided by your repositories. Skip the other st
Manual: Manual:
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need. 1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata` 2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tesseract-ocr/5/tessdata`
3. 3.
Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info. Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info.
**IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED. **IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.
@@ -264,7 +264,7 @@ sudo systemctl restart stirlingpdf.service
Remember to set the necessary environment variables before running the project if you want to customize the application the list can be seen in the main readme. Remember to set the necessary environment variables before running the project if you want to customize the application the list can be seen in the main readme.
You can do this in the terminal by using the `export` command or -D argument to java -jar command: You can do this in the terminal by using the `export` command or -D arguements to java -jar command:
```bash ```bash
export APP_HOME_NAME="Stirling PDF" export APP_HOME_NAME="Stirling PDF"

View File

@@ -1,42 +0,0 @@
# Pipeline Configuration and Usage Tutorial
## Whilst Pipelines are in alpha...
You must enable this alpha functionality by setting
```yaml
system:
enableAlphaFunctionality: true
```
To true like in the above for your `/config/settings.yml` file, after restarting Stirling-PDF you should see both UI and folder scanning enabled.
## Steps to Configure and Use Your Pipeline
1. **Access Configuration**
- Upon entering the screen, click on the **Configure** button.
2. **Enter Pipeline Name**
- Provide a name for your pipeline in the designated field.
3. **Select Operations**
- Choose the operations for your pipeline (e.g., **Split Pages**), then click **Add Operation**.
4. **Configure Operation Settings**
- Input the necessary settings for each added operation. Settings are highlighted in yellow if customization is needed.
5. **Add More Operations**
- You can add and adjust the order of multiple operations. Ensure each operation's settings are customized.
6. **Save Settings**
- Click **Save Operation Settings** after customizing settings for each operation.
7. **Validate Pipeline**
- Use the **Validation** button to check your pipeline. A green indicator signifies correct setup; a pop-out error indicates issues.
8. **Download Pipeline Configuration**
- To use the configuration for folder scanning (or save it for future use and reupload it), you can also download a JSON file in this menu. You can also pre-load this for future use by placing it in ``/pipeline/defaultWebUIConfigs/``. It will then appear in the dropdown menu for all users to use.
9. **Submit Files for Processing**
- If your pipeline is correctly set up close the configure menu, input the files and hit **Submit**.
10. **Note on Web UI Limitations**
- The current web UI version does not support operations that require multiple different types of inputs, such as adding a separate image to a PDF.

View File

@@ -1,14 +1,14 @@
<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 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> </p>
[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf) [![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf)
[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/Cn8pWhQRxZ) [![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/Cn8pWhQRxZ)
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/) [![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Frooodle/Stirling-PDF/)
[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf) [![GitHub Repo stars](https://img.shields.io/github/stars/frooodle/stirling-pdf?style=social)](https://github.com/Frooodle/stirling-pdf)
[![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/paypalme/froodleplex) [![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/paypalme/froodleplex)
[![Github Sponsor](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle) [![Github Sponser](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle)
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af) [![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Frooodle/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
This is a powerful locally hosted web based PDF manipulation tool using docker that allows you to perform various operations on PDF files, such as splitting merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application started as a 100% ChatGPT-made application and has evolved to include a wide range of features to handle all your PDF needs. This is a powerful locally hosted web based PDF manipulation tool using docker that allows you to perform various operations on PDF files, such as splitting merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application started as a 100% ChatGPT-made application and has evolved to include a wide range of features to handle all your PDF needs.
@@ -16,14 +16,16 @@ Stirling PDF makes no outbound calls for any record keeping or tracking.
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point. All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
Please feel free to submit feature requests or report bugs either through GitHub issues or on our [Discord](https://discord.gg/Cn8pWhQRxZ)
![stirling-home](images/stirling-home.png) ![stirling-home](images/stirling-home.png)
## Features ## Features
- Dark mode support. - Dark mode support.
- Custom download options (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/images/settings.png) for example) - Custom download options (see [here](https://github.com/Frooodle/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/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation) - Optional Login and Authentication support (see [here](https://github.com/Frooodle/Stirling-PDF/tree/main#login-authentication) for documentation)
## **PDF Features** ## **PDF Features**
@@ -78,39 +80,39 @@ All files and PDFs exist either exclusively on the client side, reside in server
- 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/Stirling-Tools/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/Frooodle/Stirling-PDF/blob/main/Endpoint-groups.md)
Demo of the app is available [here](https://stirlingpdf.io). username: demo, password: demo 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](https://github.com/apache/pdfbox/tree/trunk) - PDFBox
- [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](https://github.com/mozilla/pdf.js) - PDF.js
- [PDF-LIB.js](https://github.com/Hopding/pdf-lib) - PDF-LIB.js
## How to use ## How to use
### Locally ### Locally
Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/LocalRunGuide.md Please view https://github.com/Frooodle/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/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md) To see what the different versions offer please look at our [version mapping](https://github.com/Frooodle/Stirling-PDF/blob/main/Version-groups.md)
For people that don't mind about space optimization just use the latest tag. For people that don't mind about space optimization just use the latest tag.
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest?label=Stirling-PDF%20Full) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest?label=Stirling-PDF%20Full)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-lite?label=Stirling-PDF%20Lite) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-lite?label=Stirling-PDF%20Lite)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite) ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite)
Docker Run Docker Run
```bash ```
docker run -d \ docker run -d \
-p 8080:8080 \ -p 8080:8080 \
-v /location/of/trainingData:/usr/share/tessdata \ -v /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata \
-v /location/of/extraConfigs:/configs \ -v /location/of/extraConfigs:/configs \
-v /location/of/logs:/logs \ -v /location/of/logs:/logs \
-e DOCKER_ENABLE_SECURITY=false \ -e DOCKER_ENABLE_SECURITY=false \
@@ -123,7 +125,7 @@ docker run -d \
-v /location/of/customFiles:/customFiles \ -v /location/of/customFiles:/customFiles \
``` ```
Docker Compose Docker Compose
```yaml ```
version: '3.3' version: '3.3'
services: services:
stirling-pdf: stirling-pdf:
@@ -131,7 +133,7 @@ services:
ports: ports:
- '8080:8080' - '8080:8080'
volumes: volumes:
- /location/of/trainingData:/usr/share/tessdata #Required for extra OCR languages - /location/of/trainingData:/usr/share/tesseract-ocr/5/tessdata #Required for extra OCR languages
- /location/of/extraConfigs:/configs - /location/of/extraConfigs:/configs
# - /location/of/customFiles:/customFiles/ # - /location/of/customFiles:/customFiles/
# - /location/of/logs:/logs/ # - /location/of/logs:/logs/
@@ -142,19 +144,17 @@ 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/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
## Supported Languages ## 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)
- Simplified Chinese (简体中文) (zh_CN) - 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,13 +170,16 @@ Stirling PDF currently supports 26!
- 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)
## Contributing (creating issues, translations, fixing bugs, etc.) If you want to add your own language to Stirling-PDF please refer
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
And please create a PR to merge it back in so others can use it!
## How to View
1. Open a web browser and navigate to `http://localhost:8080/`
2. Use the application by following the instructions on the website.
Please see our [Contributing Guide](CONTRIBUTING.md)!
## Customisation ## Customisation
Stirling PDF allows easy customization of the app. Stirling PDF allows easy customization of the app.
@@ -190,7 +193,7 @@ This file is located in the ``/configs`` directory and follows standard YAML for
Environment variables are also supported and would override the settings file Environment variables are also supported and would override the settings file
For example in the settings.yml you have For example in the settings.yml you have
```yaml ```
system: system:
defaultLocale: 'en-US' defaultLocale: 'en-US'
``` ```
@@ -198,7 +201,7 @@ system:
To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE`` To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE``
The Current list of settings is The Current list of settings is
```yaml ```
security: security:
enableLogin: false # set to 'true' to enable login enableLogin: false # set to 'true' to enable login
csrfDisabled: true csrfDisabled: true
@@ -221,7 +224,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/Stirling-Tools/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/Frooodle/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
@@ -231,7 +234,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/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) [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)
## Login authentication ## Login authentication
@@ -262,7 +265,7 @@ For API usage you must provide a header with 'X-API-Key' and the associated API
- Redact text (Via UI not just automated way) - Redact text (Via UI not just automated way)
- Add Forms - Add Forms
- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing - Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing
- Fill forms manually or automatically - Fill forms mannual and automatic
### Q2: Why is my application downloading .htm files? ### Q2: Why is my application downloading .htm files?
This is an issue caused commonly by your NGINX configuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. ``client_max_body_size SIZE;`` Where "SIZE" is 50M for example for 50MB files. This is an issue caused commonly by your NGINX configuration. The default file upload size for NGINX is 1MB, you need to add the following in your Nginx sites-available file. ``client_max_body_size SIZE;`` Where "SIZE" is 50M for example for 50MB files.

View File

@@ -1,30 +1,21 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.2.2' id 'org.springframework.boot' version '3.1.2'
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.25.0' 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.21.0' version = '0.18.1'
sourceCompatibility = '17' sourceCompatibility = '17'
repositories { repositories {
mavenCentral() mavenCentral()
} }
licenseReport {
renderers = [new JsonReportRenderer()]
}
sourceSets { sourceSets {
main { main {
java { java {
@@ -89,24 +80,20 @@ 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.1.3' implementation 'org.springframework:spring-webmvc:6.0.15'
implementation("io.github.pixee:java-security-toolkit:1.1.2") implementation 'org.yaml:snakeyaml:2.1'
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.1'
implementation 'org.yaml:snakeyaml:2.2' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.1'
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.2'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.2'
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') { if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.2' 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:3.2.2" implementation "org.springframework.boot:spring-boot-starter-data-jpa"
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.2' testImplementation 'org.springframework.boot:spring-boot-starter-test:3.2.1'
// Batik // Batik
implementation 'org.apache.xmlgraphics:batik-all:1.17' implementation 'org.apache.xmlgraphics:batik-all:1.17'
@@ -135,30 +122,29 @@ dependencies {
//general PDF //general PDF
// https://mvnrepository.com/artifact/com.opencsv/opencsv // https://mvnrepository.com/artifact/com.opencsv/opencsv
implementation ('com.opencsv:opencsv:5.9') { implementation ('com.opencsv:opencsv:5.7.1') {
exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'commons-logging', module: 'commons-logging'
} }
implementation ('org.apache.pdfbox:pdfbox:3.0.1'){ implementation ('org.apache.pdfbox:pdfbox:2.0.29'){
exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'commons-logging', module: 'commons-logging'
} }
implementation ('org.apache.pdfbox:xmpbox:3.0.1'){ implementation ('org.apache.pdfbox:xmpbox:2.0.29'){
exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'commons-logging', module: 'commons-logging'
} }
implementation 'org.bouncycastle:bcprov-jdk18on:1.77' implementation 'org.bouncycastle:bcprov-jdk18on:1.77'
implementation 'org.bouncycastle:bcpkix-jdk18on:1.77' implementation 'org.bouncycastle:bcpkix-jdk18on:1.77'
implementation 'org.springframework.boot:spring-boot-starter-actuator:3.2.2' implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-core:1.12.3' implementation 'io.micrometer:micrometer-core'
implementation group: 'com.google.zxing', name: 'core', version: '3.5.2' implementation group: 'com.google.zxing', name: 'core', version: '3.5.2'
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation 'org.commonmark:commonmark:0.21.0' implementation 'org.commonmark:commonmark:0.21.0'
implementation 'org.commonmark:commonmark-ext-gfm-tables:0.21.0'
// https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core // https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0' implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0'
developmentOnly("org.springframework.boot:spring-boot-devtools:3.2.2") developmentOnly("org.springframework.boot:spring-boot-devtools")
compileOnly 'org.projectlombok:lombok:1.18.30' compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.28' annotationProcessor 'org.projectlombok:lombok:1.18.28'
} }
@@ -166,9 +152,6 @@ dependencies {
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
dependsOn 'spotlessApply' dependsOn 'spotlessApply'
} }
compileJava {
options.compilerArgs << '-parameters'
}
task writeVersion { task writeVersion {
def propsFile = file('src/main/resources/version.properties') def propsFile = file('src/main/resources/version.properties')

View File

@@ -1,15 +1,15 @@
apiVersion: v2 apiVersion: v2
appVersion: 0.20.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/Stirling-Tools/Stirling-PDF home: https://github.com/Frooodle/Stirling-PDF
keywords: keywords:
- stirling-pdf - stirling-pdf
- helm - helm
- charts repo - charts repo
maintainers: maintainers:
- name: Stirling-Tools - name: Frooodle
url: https://github.com/Stirling-Tools/Stirling-PDF url: https://github.com/Frooodle/Stirling-PDF
name: stirling-pdf-chart name: stirling-pdf-chart
sources: sources:
- https://github.com/Stirling-Tools/Stirling-PDF - https://github.com/Frooodle/Stirling-PDF
version: 1.0.0 version: 1.0.0

View File

@@ -16,11 +16,11 @@ commonLabels: {}
# team_name: dev # team_name: dev
envs: [] envs: []
# - name: UI_APP_NAME # - name: PP_HOME_NAME
# value: "Stirling PDF" # value: "Stirling PDF"
# - name: UI_HOME_DESCRIPTION # - name: APP_HOME_DESCRIPTION
# value: "Your locally hosted one-stop-shop for all your PDF needs." # value: "Your locally hosted one-stop-shop for all your PDF needs."
# - name: UI_APP_NAVBAR_NAME # - name: APP_NAVBAR_NAME
# value: "Stirling PDF" # value: "Stirling PDF"
# - name: ALLOW_GOOGLE_VISIBILITY # - name: ALLOW_GOOGLE_VISIBILITY
# value: "true" # value: "true"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,110 +1,310 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg <svg
width="99.537987mm"
height="95.209366mm"
viewBox="0 0 99.537987 95.209366"
version="1.1" version="1.1"
id="Layer_1" id="svg745"
x="0px"
y="0px"
viewBox="0 0 512 512"
style="enable-background:new 0 0 512 512;"
xml:space="preserve" xml:space="preserve"
sodipodi:docname="favicon.svg" inkscape:version="1.2.1 (9c6d41e4, 2022-07-14)"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)" sodipodi:docname="stirling.svg"
inkscape:export-filename="favicon.png" inkscape:export-filename="stirling.png"
inkscape:export-xdpi="96" inkscape:export-xdpi="80"
inkscape:export-ydpi="96" inkscape:export-ydpi="80"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="defs173"> id="namedview747"
pagecolor="#ffffff"
bordercolor="#666666"
<linearGradient borderopacity="1.0"
id="XMLID_5_" inkscape:showpageshadow="2"
gradientUnits="userSpaceOnUse" inkscape:pageopacity="0.0"
x1="304.496" inkscape:pagecheckerboard="0"
y1="422.9102" inkscape:deskcolor="#d1d1d1"
x2="316.036" inkscape:document-units="mm"
y2="326.2626"> showgrid="false"
<stop inkscape:zoom="0.914906"
offset="0" inkscape:cx="184.17193"
style="stop-color:#DCF1F3" inkscape:cy="509.88845"
id="stop156" /> inkscape:window-width="2293"
<stop inkscape:window-height="1387"
offset="1" inkscape:window-x="122"
style="stop-color:#C2C2C9" inkscape:window-y="25"
id="stop158" /> inkscape:window-maximized="0"
</linearGradient> inkscape:current-layer="svg745" /><defs
id="defs742"><linearGradient
</defs><sodipodi:namedview inkscape:collect="always"
id="namedview171" id="linearGradient72198"><stop
pagecolor="#ffffff" style="stop-color:#ccd6d7;stop-opacity:1;"
bordercolor="#000000" offset="0"
borderopacity="0.25" id="stop72194" /><stop
inkscape:showpageshadow="2" style="stop-color:#0f3a3f;stop-opacity:1;"
inkscape:pageopacity="0.0" offset="1"
inkscape:pagecheckerboard="0" id="stop72196" /></linearGradient><linearGradient
inkscape:deskcolor="#d1d1d1" inkscape:collect="always"
showgrid="false" id="linearGradient71928"><stop
inkscape:zoom="1.4142136" style="stop-color:#4b0005;stop-opacity:1;"
inkscape:cx="219.91021" offset="0"
inkscape:cy="232.63813" id="stop71924" /><stop
inkscape:window-width="3840" style="stop-color:#8f000c;stop-opacity:1;"
inkscape:window-height="2054" offset="1"
inkscape:window-x="2869" id="stop71926" /></linearGradient><linearGradient
inkscape:window-y="-11" inkscape:collect="always"
inkscape:window-maximized="1" id="linearGradient71920"><stop
inkscape:current-layer="XMLID_4_" /> style="stop-color:#4b0005;stop-opacity:1;"
<style offset="0"
type="text/css" id="stop71916" /><stop
id="style150"> style="stop-color:#8f000c;stop-opacity:1;"
.st0{fill:#FFFFFF;} offset="1"
.st1{fill:#C02223;} id="stop71918" /></linearGradient><linearGradient
.st2{fill:#882425;} inkscape:collect="always"
.st3{fill:url(#XMLID_5_);} id="linearGradient69598"><stop
.st4{fill:url(#XMLID_7_);} style="stop-color:#6a0007;stop-opacity:1;"
</style> offset="0"
id="stop69594" /><stop
<g style="stop-color:#b8000f;stop-opacity:1;"
id="XMLID_4_"> offset="1"
<path id="stop69596" /></linearGradient><linearGradient
id="XMLID_131_" inkscape:collect="always"
class="st1" id="linearGradient46361"><stop
d="M 347.01402,14.355825 98.978019,69.02261 C 73.825483,74.547445 55.942464,96.792175 55.942464,122.52628 v 315.06096 c 0,22.39012 16.719895,41.14548 38.819234,43.76251 L 224.8861,498.36042 339.48636,384.26465 455.76603,265.15425 453.73057,84.870162 C 453.43979,62.916214 433.08513,46.632491 411.71274,51.284984 l -28.78729,6.251786 0.14539,-13.666697 C 383.36162,24.678542 365.62399,10.284894 347.01402,14.355825 Z" style="stop-color:#f7f6f8;stop-opacity:1;"
sodipodi:nodetypes="ccssccccccccc" offset="0"
style="stroke-width:1.45391" /><path id="stop46359" /><stop
id="XMLID_117_" style="stop-color:#b7b7b5;stop-opacity:1;"
class="st2" offset="1"
d="m 383.21622,57.53677 v 285.8375 L 456.05681,265.00885 454.02135,78.763767 C 453.87595,59.863016 436.28372,45.905539 417.81914,49.97647 Z" id="stop46357" /></linearGradient><linearGradient
style="stroke-width:1.45391" /><polygon inkscape:collect="always"
id="XMLID_18_" id="linearGradient40554"><stop
class="st3" style="stop-color:#f4f2f4;stop-opacity:1;"
points="234.7,422.6 368.5,387.7 393.5,262.2 " offset="0"
style="fill:url(#XMLID_5_)" id="stop40550" /><stop
transform="matrix(1.4556308,0,0,1.4548265,-116.73161,-116.45231)" /> style="stop-color:#57767b;stop-opacity:1;"
<linearGradient offset="1"
id="XMLID_7_" id="stop40552" /></linearGradient><linearGradient
gradientUnits="userSpaceOnUse" inkscape:collect="always"
x1="223.0838" id="linearGradient39095"><stop
y1="372.7559" style="stop-color:#285459;stop-opacity:1;"
x2="241.4174" offset="0"
y2="114.557" id="stop39093" /><stop
gradientTransform="matrix(1.4539039,0,0,1.4539039,-116.19976,-116.20474)"> style="stop-color:#a6b6af;stop-opacity:1;"
<stop offset="1"
offset="0" id="stop39091" /></linearGradient><linearGradient
style="stop-color:#DCF1F3" inkscape:collect="always"
id="stop163" /> id="linearGradient36672"><stop
<stop style="stop-color:#da453f;stop-opacity:1;"
offset="1" offset="0"
style="stop-color:#C2C2C9" id="stop36668" /><stop
id="stop165" /> style="stop-color:#a60008;stop-opacity:1;"
</linearGradient> offset="1"
<path id="stop36670" /></linearGradient><linearGradient
id="XMLID_6_" inkscape:collect="always"
class="st4" id="linearGradient19524"><stop
d="m 282.89686,214.84917 c 0,0 -22.24473,-28.93269 -38.67384,-36.78377 -10.46811,-4.94327 -26.02489,-6.83335 -38.23768,-0.72695 -18.02841,9.0142 -19.91848,34.31213 -3.34397,44.34406 3.92553,2.47165 9.15959,4.50711 15.99294,6.10641 36.63838,8.43264 97.12077,25.87949 89.70587,96.10304 0,0 -4.21633,65.86185 -73.56753,73.42215 -12.2128,1.30851 -24.57098,0.43617 -36.493,-2.32625 -16.42911,-3.63476 -45.50719,-11.04967 -59.75545,-19.91849 l -2.61703,-75.16682 h 6.97875 c 0,0 13.81208,33.43978 53.06749,49.57812 7.26952,2.90781 15.26599,4.07093 22.97168,2.90781 9.74116,-1.45391 21.22699,-6.68796 25.87949,-22.53551 0,0 7.85108,-23.11707 -32.85823,-35.76604 -32.56744,-10.17733 -63.24481,-20.64543 -75.89378,-54.95757 -5.961,-16.28371 -6.97874,-34.31212 -2.90781,-51.61358 5.37944,-22.53551 20.79082,-54.23062 64.40794,-67.89732 0,0 57.28381,-15.55677 96.53922,5.52484 l -1.74468,89.70587 z" style="stop-color:#3a4b4f;stop-opacity:1;"
style="fill:url(#XMLID_7_);stroke-width:1.45391" /> offset="0"
</g> id="stop19522" /><stop
</svg> style="stop-color:#617979;stop-opacity:0.97461927;"
offset="1"
id="stop19520" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient17996"><stop
style="stop-color:#401016;stop-opacity:1;"
offset="0"
id="stop17994" /><stop
style="stop-color:#761f19;stop-opacity:1;"
offset="1"
id="stop17992" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient15569"><stop
style="stop-color:#8ea8ad;stop-opacity:1;"
offset="0"
id="stop15565" /><stop
style="stop-color:#e9e7eb;stop-opacity:1;"
offset="1"
id="stop15567" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient15557"><stop
style="stop-color:#9b0e11;stop-opacity:1;"
offset="0"
id="stop15553" /><stop
style="stop-color:#370707;stop-opacity:1;"
offset="1"
id="stop15555" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient15557"
id="linearGradient15559"
x1="120.06672"
y1="63.25761"
x2="135.16347"
y2="78.078682"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient15569"
id="linearGradient15571"
x1="127.97037"
y1="101.66144"
x2="133.88971"
y2="104.77026"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient17996"
id="linearGradient17998"
x1="117.9284"
y1="86.055084"
x2="130.67392"
y2="76.945541"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19524"
id="linearGradient19528"
x1="130.98172"
y1="82.402977"
x2="135.72115"
y2="86.45166"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient36672"
id="linearGradient36674"
x1="63.797714"
y1="74.81752"
x2="96.636673"
y2="120.29293"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient39095"
id="linearGradient39097"
x1="120.54506"
y1="124.76902"
x2="128.04152"
y2="126.0704"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient40554"
id="linearGradient40556"
x1="113.84585"
y1="114.04285"
x2="119.65858"
y2="128.50244"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient46361"
id="linearGradient46363"
x1="73.993439"
y1="114.13906"
x2="94.845322"
y2="71.247383"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient69598"
id="linearGradient69600"
x1="95.854446"
y1="114.66749"
x2="103.77842"
y2="120.1887"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient71920"
id="linearGradient71922"
x1="98.580376"
y1="87.186958"
x2="118.09738"
y2="101.19449"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient71928"
id="linearGradient71930"
x1="78.278267"
y1="97.433273"
x2="92.313202"
y2="104.33479"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient72198"
id="linearGradient72200"
x1="125.76636"
y1="138.46817"
x2="123.3327"
y2="126.03291"
gradientUnits="userSpaceOnUse" /></defs><g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="background"
style="display:inline"
sodipodi:insensitive="true"
transform="translate(-51.420144,-44.470286)"><rect
style="display:inline;fill:#ccd6d7;fill-opacity:1;stroke:none;stroke-width:4.13755;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.2"
id="rect72067"
width="99.481979"
height="94.999512"
x="51.476147"
y="44.680138" /></g><g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="shadow"
style="display:inline"
sodipodi:insensitive="true"
transform="translate(-51.420144,-44.470286)"><path
style="display:inline;fill:url(#linearGradient72200);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 84.146049,134.73858 c 0,0 11.721038,2.48294 17.938661,2.91673 6.21763,0.43378 14.75251,0.59994 22.41237,-0.43379 8.01008,-1.081 13.19907,-2.22733 14.50043,-2.66112 1.30136,-0.43379 4.00784,-1.24297 4.15244,-2.25514 0.1446,-1.01217 -3.4703,-2.74733 -6.21763,-3.32571 -2.74732,-0.57838 -12.72444,-1.44596 -14.89337,-1.44596 -2.16894,0 -37.892901,7.20499 -37.892901,7.20499 z"
id="path72192"
sodipodi:nodetypes="cssssssc" /></g><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Origami"
style="display:inline"
sodipodi:insensitive="true"
transform="translate(-51.420144,-44.470286)"><path
style="display:inline;fill:url(#linearGradient40556);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 84.460552,134.86721 35.165798,-6.85679 16.15467,-42.7383 z"
id="path960"
sodipodi:nodetypes="cccc" /><path
style="fill:url(#linearGradient15571);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 135.71493,85.428056 0.3984,45.049024 -9.66213,-20.46173 z"
id="path964"
sodipodi:nodetypes="cccc" /><path
style="display:inline;fill:url(#linearGradient39097);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 119.60518,128.00293 16.5337,2.48693 -9.68769,-20.5512 z"
id="path966"
sodipodi:nodetypes="cccc" /><path
style="display:inline;fill:url(#linearGradient15559);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 118.42209,57.022622 12.70423,-2.766809 -0.0785,25.087175 -12.43878,-13.376518 z"
id="path968"
sodipodi:nodetypes="ccccc" /><path
style="display:inline;fill:url(#linearGradient19528);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 135.72114,85.386768 -4.84219,-6.459493 0.0305,11.126604 z"
id="path970"
sodipodi:nodetypes="cccc" /><path
style="display:inline;fill:url(#linearGradient17998);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 119.10273,65.682415 11.96883,13.44935 -0.0899,10.819868 -11.88577,11.430427 z"
id="path972"
sodipodi:nodetypes="ccccc" /><path
style="display:inline;fill:url(#linearGradient36674);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 62.145635,130.15618 62.043392,65.435258 c 0,0 0.179321,-2.778132 1.89516,-4.036097 1.874923,-1.374597 4.341768,-1.894096 4.341768,-1.894096 l 50.91788,-11.349167 -0.0113,53.144272 -34.733274,33.56547 z"
id="path958"
sodipodi:nodetypes="ccsccccc" /></g><g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Letter"
style="display:inline"
sodipodi:insensitive="true"
transform="translate(-51.420144,-44.470286)"><path
style="display:inline;fill:url(#linearGradient69600);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 94.780881,91.406803 16.870379,17.074877 -23.723345,23.00249 -18.122131,-17.99816 5.497473,-2.80607 18.404054,-0.0511 2.35163,-8.23071 z"
id="path54894"
sodipodi:nodetypes="cccccccc" /><path
style="display:inline;fill:url(#linearGradient71930);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 72.440405,92.224764 16.15467,15.745686 4.089788,-6.79927 z"
id="path54892" /><path
style="display:inline;fill:url(#linearGradient71922);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 95.138739,84.965385 1.124691,-14.109776 22.92453,22.286787 0.008,8.164604 -3.28863,3.16649 z"
id="path54890"
sodipodi:nodetypes="cccccc"
inkscape:label="path54890" /><path
style="display:inline;fill:url(#linearGradient46363);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 95.138739,84.965385 h 1.226936 l -0.05112,-14.109776 c 0,0 -5.776827,-3.220709 -12.167126,-2.40275 -6.390296,0.817957 -8.151582,2.1248 -10.58233,4.396523 -1.90229,1.777838 -2.913974,3.527446 -3.987546,7.157132 -0.512646,1.733226 -0.281963,5.988892 0.613471,8.537436 0.664591,1.891528 2.453873,4.294281 4.958868,6.134686 2.662335,1.956002 8.281825,3.527443 8.281825,3.527443 0,0 5.134614,1.887351 5.572338,4.294281 0.308137,1.69437 -0.102243,3.22071 -1.635914,4.95887 -1.258314,1.42609 -3.62969,1.99377 -6.288054,1.07357 -2.658364,-0.92021 -6.139514,-3.85065 -7.106009,-4.90775 -0.817958,-0.89464 -2.820665,-3.06173 -2.890231,-4.294021 -0.01209,-0.214205 -1.229505,-0.0963 -1.229505,-0.0963 l -0.0723,14.256941 5.879073,2.24938 c 0,0 5.214483,1.78929 8.946415,1.43143 3.731934,-0.35786 7.617235,-0.51122 11.604778,-5.16336 3.987542,-4.65213 3.680812,-12.831715 2.147141,-15.899056 -1.533673,-3.067344 -3.561212,-6.138812 -10.480087,-8.281826 -5.776829,-1.789283 -7.872846,-3.01622 -8.128458,-4.396524 -0.255611,-1.380305 0.0091,-4.253646 2.760607,-5.214481 3.220711,-1.124693 5.623462,-0.05112 7.05489,1.12469 1.431425,1.175817 5.572339,5.623462 5.572339,5.623462 z"
id="path46355"
sodipodi:nodetypes="cccssssscssssscccssssssscc" /></g></svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,31 +0,0 @@
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/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

View File

@@ -1,30 +0,0 @@
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

View File

@@ -1,31 +0,0 @@
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/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

View File

@@ -1,31 +0,0 @@
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/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

View File

@@ -1,30 +0,0 @@
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

View File

@@ -1,31 +0,0 @@
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/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

0
gradlew vendored Executable file → Normal file
View File

View File

@@ -16,7 +16,7 @@ public class PropSync {
Map<String, String> enProps = linesToProps(enLines); Map<String, String> enProps = linesToProps(enLines);
for (File file : files) { for (File file : files) {
if (!"messages_en_GB.properties".equals(file.getName())) { if (!file.getName().equals("messages_en_GB.properties")) {
System.out.println("Processing file: " + file.getName()); System.out.println("Processing file: " + file.getName());
List<String> lines; List<String> lines;
try { try {

View File

@@ -0,0 +1,37 @@
import cv2
import sys
import argparse
import numpy as np
def is_blank_image(image_path, threshold=10, white_percent=99, white_value=255, blur_size=5):
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if image is None:
print(f"Error: Unable to read the image file: {image_path}")
return False
# Apply Gaussian blur to reduce noise
blurred_image = cv2.GaussianBlur(image, (blur_size, blur_size), 0)
_, thresholded_image = cv2.threshold(blurred_image, white_value - threshold, white_value, cv2.THRESH_BINARY)
# Calculate the percentage of white pixels in the thresholded image
white_pixels = np.sum(thresholded_image == white_value)
white_pixel_percentage = (white_pixels / thresholded_image.size) * 100
print(f"Page has white pixel percent of {white_pixel_percentage}")
return white_pixel_percentage >= white_percent
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Detect if an image is considered blank or not.')
parser.add_argument('image_path', help='The path to the image file.')
parser.add_argument('-t', '--threshold', type=int, default=10, help='Threshold for determining white pixels. The default value is 10.')
parser.add_argument('-w', '--white_percent', type=float, default=99, help='The percentage of white pixels for an image to be considered blank. The default value is 99.')
args = parser.parse_args()
blank = is_blank_image(args.image_path, args.threshold, args.white_percent)
# Return code 1: The image is considered blank.
# Return code 0: The image is not considered blank.
sys.exit(int(blank))

View File

@@ -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/Stirling-Tools/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar" echo "Trying to download from: 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 curl -L -o app-security.jar https://github.com/Frooodle/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/Stirling-Tools/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar" echo "Trying to download from: 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 curl -L -o app-security.jar https://github.com/Frooodle/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

View File

@@ -2,15 +2,11 @@
# Copy the original tesseract-ocr files to the volume directory without overwriting existing files # Copy the original tesseract-ocr files to the volume directory without overwriting existing files
echo "Copying original files without overwriting existing files" echo "Copying original files without overwriting existing files"
mkdir -p /usr/share/tessdata mkdir -p /usr/share/tesseract-ocr
cp -rn /usr/share/tessdata-original/* /usr/share/tessdata cp -rn /usr/share/tesseract-ocr-original/* /usr/share/tesseract-ocr
if [ -d /usr/share/tesseract-ocr/4.00/tessdata ]; then if [ -d /usr/share/tesseract-ocr/4.00/tessdata ]; then
cp -r /usr/share/tesseract-ocr/4.00/tessdata/* /usr/share/tessdata || true; cp -r /usr/share/tesseract-ocr/4.00/tessdata/* /usr/share/tesseract-ocr/5/tessdata/ || true;
fi
if [ -d /usr/share/tesseract-ocr/5/tessdata ]; then
cp -r /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata || true;
fi fi
# Check if TESSERACT_LANGS environment variable is set and is not empty # Check if TESSERACT_LANGS environment variable is set and is not empty

View File

@@ -1,64 +0,0 @@
/*
* 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;
}
}

View File

@@ -1,170 +0,0 @@
/*
* 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;
}
}

View File

@@ -1,176 +0,0 @@
/*
* 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;
}
}

View File

@@ -1,134 +0,0 @@
/*
* 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));
}
}

View File

@@ -1,82 +0,0 @@
/*
* 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();
}
}

View File

@@ -6,8 +6,6 @@ import java.net.Socket;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import io.github.pixee.security.SystemCommand;
public class LibreOfficeListener { public class LibreOfficeListener {
private static final long ACTIVITY_TIMEOUT = 20 * 60 * 1000; // 20 minutes private static final long ACTIVITY_TIMEOUT = 20 * 60 * 1000; // 20 minutes
@@ -46,7 +44,7 @@ public class LibreOfficeListener {
} }
// Start the listener process // Start the listener process
process = SystemCommand.runCommand(Runtime.getRuntime(), "unoconv --listener"); process = Runtime.getRuntime().exec("unoconv --listener");
lastActivityTime = System.currentTimeMillis(); lastActivityTime = System.currentTimeMillis();
// Start a background thread to monitor the activity timeout // Start a background thread to monitor the activity timeout

View File

@@ -10,8 +10,6 @@ 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 io.github.pixee.security.SystemCommand;
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;
@@ -26,7 +24,7 @@ public class SPdfApplication {
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 && "true".equalsIgnoreCase(browserOpenEnv); boolean browserOpen = browserOpenEnv != null && browserOpenEnv.equalsIgnoreCase("true");
if (browserOpen) { if (browserOpen) {
try { try {
@@ -36,7 +34,7 @@ public class SPdfApplication {
Runtime rt = Runtime.getRuntime(); Runtime rt = Runtime.getRuntime();
if (os.contains("win")) { if (os.contains("win")) {
// For Windows // For Windows
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url); rt.exec("rundll32 url.dll,FileProtocolHandler " + url);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@@ -1,15 +1,8 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;
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.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@@ -31,15 +24,8 @@ public class AppConfig {
@Bean(name = "appVersion") @Bean(name = "appVersion")
public String appVersion() { public String appVersion() {
Resource resource = new ClassPathResource("version.properties"); String version = getClass().getPackage().getImplementationVersion();
Properties props = new Properties(); return (version != null) ? version : "0.0.0";
try {
props.load(resource.getInputStream());
return props.getProperty("version");
} catch (IOException e) {
e.printStackTrace();
}
return "0.0.0";
} }
@Bean(name = "homeText") @Bean(name = "homeText")
@@ -71,17 +57,4 @@ public class AppConfig {
if (appName == null) appName = System.getenv("rateLimit"); if (appName == null) appName = System.getenv("rateLimit");
return (appName != null) ? Boolean.valueOf(appName) : false; return (appName != null) ? Boolean.valueOf(appName) : false;
} }
@Bean(name = "RunningInDocker")
public boolean runningInDocker() {
return Files.exists(Paths.get("/.dockerenv"));
}
@Bean(name = "bookAndHtmlFormatsInstalled")
public boolean bookAndHtmlFormatsInstalled() {
return applicationProperties
.getSystem()
.getCustomApplications()
.isInstallBookAndHtmlFormats();
}
} }

View File

@@ -79,22 +79,12 @@ public class ConfigInitializer
return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : ""; return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : "";
}; };
Function<String, Integer> getIndentationLevel =
line -> {
int count = 0;
for (char ch : line.toCharArray()) {
if (ch == ' ') count++;
else break;
}
return count;
};
Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet()); Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
for (String line : templateLines) { for (String line : templateLines) {
String key = extractKey.apply(line); String key = extractKey.apply(line);
if ("AutomaticallyGenerated:".equalsIgnoreCase(line.trim())) { if (line.trim().equalsIgnoreCase("AutomaticallyGenerated:")) {
insideAutoGenerated = true; insideAutoGenerated = true;
mergedLines.add(line); mergedLines.add(line);
continue; continue;
@@ -144,77 +134,10 @@ public class ConfigInitializer
.map(extractKey) .map(extractKey)
.anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey)); .anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey));
if (!isPresentInTemplate && !isCommented.apply(userLine)) { if (!isPresentInTemplate && !isCommented.apply(userLine)) {
if (!childOfTemplateEntry( mergedLines.add(userLine);
isCommented,
extractKey,
getIndentationLevel,
userLines,
userLine,
templateLines)) {
// check if userLine is a child of a entry within templateLines or not, if child
// of parent in templateLines then dont add to mergedLines, if anything else
// then add
mergedLines.add(userLine);
}
} }
} }
Files.write(outputPath, mergedLines, StandardCharsets.UTF_8); Files.write(outputPath, mergedLines, StandardCharsets.UTF_8);
} }
// New method to check if a userLine is a child of an entry in templateLines
boolean childOfTemplateEntry(
Function<String, Boolean> isCommented,
Function<String, String> extractKey,
Function<String, Integer> getIndentationLevel,
List<String> userLines,
String userLine,
List<String> templateLines) {
String userKey = extractKey.apply(userLine).trim();
int userIndentation = getIndentationLevel.apply(userLine);
// Start by assuming the line is not a child of an entry in templateLines
boolean isChild = false;
// Iterate backwards through userLines from the current line to find any parent
for (int i = userLines.indexOf(userLine) - 1; i >= 0; i--) {
String potentialParentLine = userLines.get(i);
int parentIndentation = getIndentationLevel.apply(potentialParentLine);
// Check if we've reached a potential parent based on indentation
if (parentIndentation < userIndentation) {
String parentKey = extractKey.apply(potentialParentLine).trim();
// Now, check if this potential parent or any of its parents exist in templateLines
boolean parentExistsInTemplate =
templateLines.stream()
.filter(line -> !isCommented.apply(line)) // Skip commented lines
.anyMatch(
templateLine -> {
String templateKey =
extractKey.apply(templateLine).trim();
return parentKey.equalsIgnoreCase(templateKey);
});
if (!parentExistsInTemplate) {
// If the parent does not exist in template, check the next level parent
userIndentation =
parentIndentation; // Update userIndentation to the parent's indentation
// for next iteration
if (parentIndentation == 0) {
// If we've reached the top-level parent and it's not in template, the
// original line is considered not a child
isChild = false;
break;
}
} else {
// If any parent exists in template, the original line is considered a child
isChild = true;
break;
}
}
}
return isChild; // Return true if the line is not a child of any entry in templateLines
}
} }

View File

@@ -9,14 +9,11 @@ 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.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Service @Service
@DependsOn({"bookAndHtmlFormatsInstalled"})
public class EndpointConfiguration { public class EndpointConfiguration {
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>(); private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
@@ -24,14 +21,9 @@ public class EndpointConfiguration {
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private boolean bookAndHtmlFormatsInstalled;
@Autowired @Autowired
public EndpointConfiguration( public EndpointConfiguration(ApplicationProperties applicationProperties) {
ApplicationProperties applicationProperties,
@Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) {
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled;
init(); init();
processEnvironmentConfigs(); processEnvironmentConfigs();
} }
@@ -140,6 +132,7 @@ public class EndpointConfiguration {
// CLI // CLI
addEndpointToGroup("CLI", "compress-pdf"); addEndpointToGroup("CLI", "compress-pdf");
addEndpointToGroup("CLI", "extract-image-scans"); addEndpointToGroup("CLI", "extract-image-scans");
addEndpointToGroup("CLI", "remove-blanks");
addEndpointToGroup("CLI", "repair"); addEndpointToGroup("CLI", "repair");
addEndpointToGroup("CLI", "pdf-to-pdfa"); addEndpointToGroup("CLI", "pdf-to-pdfa");
addEndpointToGroup("CLI", "file-to-pdf"); addEndpointToGroup("CLI", "file-to-pdf");
@@ -152,12 +145,6 @@ public class EndpointConfiguration {
addEndpointToGroup("CLI", "ocr-pdf"); addEndpointToGroup("CLI", "ocr-pdf");
addEndpointToGroup("CLI", "html-to-pdf"); addEndpointToGroup("CLI", "html-to-pdf");
addEndpointToGroup("CLI", "url-to-pdf"); addEndpointToGroup("CLI", "url-to-pdf");
addEndpointToGroup("CLI", "book-to-pdf");
addEndpointToGroup("CLI", "pdf-to-book");
// Calibre
addEndpointToGroup("Calibre", "book-to-pdf");
addEndpointToGroup("Calibre", "pdf-to-book");
// python // python
addEndpointToGroup("Python", "extract-image-scans"); addEndpointToGroup("Python", "extract-image-scans");
@@ -217,7 +204,6 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "split-by-size-or-count"); addEndpointToGroup("Java", "split-by-size-or-count");
addEndpointToGroup("Java", "overlay-pdf"); addEndpointToGroup("Java", "overlay-pdf");
addEndpointToGroup("Java", "split-pdf-by-sections"); addEndpointToGroup("Java", "split-pdf-by-sections");
addEndpointToGroup("Java", "remove-blanks");
// Javascript // Javascript
addEndpointToGroup("Javascript", "pdf-organizer"); addEndpointToGroup("Javascript", "pdf-organizer");
@@ -229,9 +215,7 @@ public class EndpointConfiguration {
private void processEnvironmentConfigs() { private void processEnvironmentConfigs() {
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove(); List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove(); List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
if (!bookAndHtmlFormatsInstalled) {
groupsToRemove.add("Calibre");
}
if (endpointsToRemove != null) { if (endpointsToRemove != null) {
for (String endpoint : endpointsToRemove) { for (String endpoint : endpointsToRemove) {
disableEndpoint(endpoint.trim()); disableEndpoint(endpoint.trim());

View File

@@ -56,7 +56,6 @@ public class MetricsFilter extends OncePerRequestFilter {
.register(meterRegistry); .register(meterRegistry);
counter.increment(); counter.increment();
// System.out.println("Counted");
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);

View File

@@ -1,69 +0,0 @@
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("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
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 (bookAndHtmlFormatsInstalled) {
List<String> tmpList = new ArrayList<>();
tmpList = new ArrayList<>();
tmpList.addAll(Arrays.asList("apk add --no-cache calibre"));
commands.add(tmpList);
}
if (!commands.isEmpty()) {
// Run the command
if (runningInDocker) {
List<String> tmpList = new ArrayList<>();
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.");
}
}
}
}

View File

@@ -9,9 +9,6 @@ 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.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
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;
@@ -47,11 +44,6 @@ public class SecurityConfiguration {
@Autowired private FirstLoginFilter firstLoginFilter; @Autowired private FirstLoginFilter firstLoginFilter;
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@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);
@@ -61,14 +53,6 @@ public class SecurityConfiguration {
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.sessionManagement(
sessionManagement ->
sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(3)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?logout=true"));
http.formLogin( http.formLogin(
formLogin -> formLogin ->
formLogin formLogin
@@ -118,9 +102,7 @@ public class SecurityConfiguration {
|| 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(
"/api/v1/info/status");
}) })
.permitAll() .permitAll()
.anyRequest() .anyRequest()
@@ -131,7 +113,6 @@ public class SecurityConfiguration {
http.csrf(csrf -> csrf.disable()) http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); .authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
} }
return http.build(); return http.build();
} }

View File

@@ -103,7 +103,6 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
contextPath + "/css/", contextPath + "/css/",
contextPath + "/js/", contextPath + "/js/",
contextPath + "/pdfjs/", contextPath + "/pdfjs/",
contextPath + "/api/v1/info/status",
contextPath + "/site.webmanifest" contextPath + "/site.webmanifest"
}; };

View File

@@ -1,14 +1,13 @@
package stirling.software.SPDF.controller.api; package stirling.software.SPDF.controller.api;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -38,7 +37,9 @@ public class CropController {
description = description =
"This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO") "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException { public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
PDDocument sourceDocument = Loader.loadPDF(form.getFileInput().getBytes());
PDDocument sourceDocument =
PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()));
PDDocument newDocument = new PDDocument(); PDDocument newDocument = new PDDocument();
@@ -52,8 +53,7 @@ public class CropController {
// Create a new page with the size of the source page // Create a new page with the size of the source page
PDPage newPage = new PDPage(sourcePage.getMediaBox()); PDPage newPage = new PDPage(sourcePage.getMediaBox());
newDocument.addPage(newPage); newDocument.addPage(newPage);
PDPageContentStream contentStream = PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
new PDPageContentStream(newDocument, newPage, AppendMode.OVERWRITE, true, true);
// Import the source page as a form XObject // Import the source page as a form XObject
PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i); PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);

View File

@@ -1,17 +1,16 @@
package stirling.software.SPDF.controller.api; package stirling.software.SPDF.controller.api;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility; import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -28,7 +27,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.general.MergePdfsRequest; import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -86,8 +84,8 @@ public class MergeController {
}; };
case "byPDFTitle": case "byPDFTitle":
return (file1, file2) -> { return (file1, file2) -> {
try (PDDocument doc1 = Loader.loadPDF(file1.getBytes()); try (PDDocument doc1 = PDDocument.load(file1.getInputStream());
PDDocument doc2 = Loader.loadPDF(file2.getBytes())) { PDDocument doc2 = PDDocument.load(file2.getInputStream())) {
String title1 = doc1.getDocumentInformation().getTitle(); String title1 = doc1.getDocumentInformation().getTitle();
String title2 = doc2.getDocumentInformation().getTitle(); String title2 = doc2.getDocumentInformation().getTitle();
return title1.compareTo(title2); return title1.compareTo(title2);
@@ -108,7 +106,6 @@ public class MergeController {
"This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO") "This endpoint merges multiple PDF files into a single PDF file. The merged file will contain all pages from the input files in the order they were provided. Input:PDF Output:PDF Type:MISO")
public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form) public ResponseEntity<byte[]> mergePdfs(@ModelAttribute MergePdfsRequest form)
throws IOException { throws IOException {
List<File> filesToDelete = new ArrayList<File>();
try { try {
MultipartFile[] files = form.getFileInput(); MultipartFile[] files = form.getFileInput();
Arrays.sort(files, getSortComparator(form.getSortType())); Arrays.sort(files, getSortComparator(form.getSortType()));
@@ -116,27 +113,20 @@ public class MergeController {
PDFMergerUtility mergedDoc = new PDFMergerUtility(); PDFMergerUtility mergedDoc = new PDFMergerUtility();
ByteArrayOutputStream docOutputstream = new ByteArrayOutputStream(); ByteArrayOutputStream docOutputstream = new ByteArrayOutputStream();
for (MultipartFile multipartFile : files) { for (MultipartFile file : files) {
File tempFile = GeneralUtils.convertMultipartFileToFile(multipartFile); mergedDoc.addSource(new ByteArrayInputStream(file.getBytes()));
filesToDelete.add(tempFile);
mergedDoc.addSource(tempFile);
} }
mergedDoc.setDestinationFileName( mergedDoc.setDestinationFileName(
files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf"); files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
mergedDoc.setDestinationStream(docOutputstream); mergedDoc.setDestinationStream(docOutputstream);
mergedDoc.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
mergedDoc.mergeDocuments(null);
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
docOutputstream.toByteArray(), mergedDoc.getDestinationFileName()); docOutputstream.toByteArray(), mergedDoc.getDestinationFileName());
} catch (Exception ex) { } catch (Exception ex) {
logger.error("Error in merge pdf process", ex); logger.error("Error in merge pdf process", ex);
throw ex; throw ex;
} finally {
for (File file : filesToDelete) {
file.delete();
}
} }
} }
} }

View File

@@ -4,7 +4,6 @@ import java.awt.Color;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -21,7 +20,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -59,7 +57,7 @@ public class MultiPageLayoutController {
: (int) Math.sqrt(pagesPerSheet); : (int) Math.sqrt(pagesPerSheet);
int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet); int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);
PDDocument sourceDocument = Loader.loadPDF(file.getBytes()); PDDocument sourceDocument = PDDocument.load(file.getInputStream());
PDDocument newDocument = new PDDocument(); PDDocument newDocument = new PDDocument();
PDPage newPage = new PDPage(PDRectangle.A4); PDPage newPage = new PDPage(PDRectangle.A4);
newDocument.addPage(newPage); newDocument.addPage(newPage);
@@ -137,7 +135,6 @@ public class MultiPageLayoutController {
byte[] result = baos.toByteArray(); byte[] result = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
result, result,
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "") file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf");
+ "_layoutChanged.pdf");
} }
} }

View File

@@ -3,13 +3,11 @@ package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
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.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.Overlay; import org.apache.pdfbox.multipdf.Overlay;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -20,7 +18,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -56,7 +53,7 @@ public class PdfOverlayController {
// "FixedRepeatOverlay" // "FixedRepeatOverlay"
int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode
try (PDDocument basePdf = Loader.loadPDF(baseFile.getBytes()); try (PDDocument basePdf = PDDocument.load(baseFile.getInputStream());
Overlay overlay = new Overlay()) { Overlay overlay = new Overlay()) {
Map<Integer, String> overlayGuide = Map<Integer, String> overlayGuide =
prepareOverlayGuide( prepareOverlayGuide(
@@ -77,8 +74,7 @@ public class PdfOverlayController {
overlay.overlay(overlayGuide).save(outputStream); overlay.overlay(overlayGuide).save(outputStream);
byte[] data = outputStream.toByteArray(); byte[] data = outputStream.toByteArray();
String outputFilename = String outputFilename =
Filenames.toSimpleFileName(baseFile.getOriginalFilename()) baseFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
.replaceFirst("[.][^.]+$", "")
+ "_overlayed.pdf"; // Remove file extension and append .pdf + "_overlayed.pdf"; // Remove file extension and append .pdf
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
@@ -135,10 +131,10 @@ public class PdfOverlayController {
overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length; overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length;
} }
try (PDDocument overlayPdf = Loader.loadPDF(overlayFiles[overlayFileIndex])) { try (PDDocument overlayPdf = PDDocument.load(overlayFiles[overlayFileIndex])) {
PDDocument singlePageDocument = new PDDocument(); PDDocument singlePageDocument = new PDDocument();
singlePageDocument.addPage(overlayPdf.getPage(pageCountInCurrentOverlay)); singlePageDocument.addPage(overlayPdf.getPage(pageCountInCurrentOverlay));
File tempFile = Files.createTempFile("overlay-page-", ".pdf").toFile(); File tempFile = File.createTempFile("overlay-page-", ".pdf");
singlePageDocument.save(tempFile); singlePageDocument.save(tempFile);
singlePageDocument.close(); singlePageDocument.close();
@@ -151,7 +147,7 @@ public class PdfOverlayController {
} }
private int getNumberOfPages(File file) throws IOException { private int getNumberOfPages(File file) throws IOException {
try (PDDocument doc = Loader.loadPDF(file)) { try (PDDocument doc = PDDocument.load(file)) {
return doc.getNumberOfPages(); return doc.getNumberOfPages();
} }
} }
@@ -163,7 +159,7 @@ public class PdfOverlayController {
File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length]; File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length];
// Load the overlay document to check its page count // Load the overlay document to check its page count
try (PDDocument overlayPdf = Loader.loadPDF(overlayFile)) { try (PDDocument overlayPdf = PDDocument.load(overlayFile)) {
int overlayPageCount = overlayPdf.getNumberOfPages(); int overlayPageCount = overlayPdf.getNumberOfPages();
if ((basePageIndex - 1) % overlayPageCount < overlayPageCount) { if ((basePageIndex - 1) % overlayPageCount < overlayPageCount) {
overlayGuide.put(basePageIndex, overlayFile.getAbsolutePath()); overlayGuide.put(basePageIndex, overlayFile.getAbsolutePath());
@@ -185,7 +181,7 @@ public class PdfOverlayController {
int repeatCount = counts[i]; int repeatCount = counts[i];
// Load the overlay document to check its page count // Load the overlay document to check its page count
try (PDDocument overlayPdf = Loader.loadPDF(overlayFile)) { try (PDDocument overlayPdf = PDDocument.load(overlayFile)) {
int overlayPageCount = overlayPdf.getNumberOfPages(); int overlayPageCount = overlayPdf.getNumberOfPages();
for (int j = 0; j < repeatCount; j++) { for (int j = 0; j < repeatCount; j++) {
for (int page = 0; page < overlayPageCount; page++) { for (int page = 0; page < overlayPageCount; page++) {

View File

@@ -4,7 +4,6 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -16,7 +15,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -44,7 +42,7 @@ public class RearrangePagesPDFController {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
String pagesToDelete = request.getPageNumbers(); String pagesToDelete = request.getPageNumbers();
PDDocument document = Loader.loadPDF(pdfFile.getBytes()); PDDocument document = PDDocument.load(pdfFile.getBytes());
// Split the page order string into an array of page numbers or range of numbers // Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pagesToDelete.split(","); String[] pageOrderArr = pagesToDelete.split(",");
@@ -58,9 +56,7 @@ public class RearrangePagesPDFController {
} }
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_pages.pdf");
.replaceFirst("[.][^.]+$", "")
+ "_removed_pages.pdf");
} }
private List<Integer> removeFirst(int totalPages) { private List<Integer> removeFirst(int totalPages) {
@@ -183,7 +179,7 @@ public class RearrangePagesPDFController {
String sortType = request.getCustomMode(); String sortType = request.getCustomMode();
try { try {
// Load the input PDF // Load the input PDF
PDDocument document = Loader.loadPDF(pdfFile.getBytes()); PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Split the page order string into an array of page numbers or range of numbers // Split the page order string into an array of page numbers or range of numbers
String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0];
@@ -214,8 +210,7 @@ public class RearrangePagesPDFController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
.replaceFirst("[.][^.]+$", "")
+ "_rearranged.pdf"); + "_rearranged.pdf");
} catch (IOException e) { } catch (IOException e) {
logger.error("Failed rearranging documents", e); logger.error("Failed rearranging documents", e);

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.PDPageTree;
@@ -15,7 +14,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -39,7 +37,7 @@ public class RotationController {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
Integer angle = request.getAngle(); Integer angle = request.getAngle();
// Load the PDF document // Load the PDF document
PDDocument document = Loader.loadPDF(pdfFile.getBytes()); PDDocument document = PDDocument.load(pdfFile.getBytes());
// Get the list of pages in the document // Get the list of pages in the document
PDPageTree pages = document.getPages(); PDPageTree pages = document.getPages();
@@ -50,8 +48,6 @@ public class RotationController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_rotated.pdf");
.replaceFirst("[.][^.]+$", "")
+ "_rotated.pdf");
} }
} }

View File

@@ -5,7 +5,6 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -22,7 +21,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -68,7 +66,7 @@ public class ScalePagesController {
PDRectangle targetSize = sizeMap.get(targetPDRectangle); PDRectangle targetSize = sizeMap.get(targetPDRectangle);
PDDocument sourceDocument = Loader.loadPDF(file.getBytes()); PDDocument sourceDocument = PDDocument.load(file.getBytes());
PDDocument outputDocument = new PDDocument(); PDDocument outputDocument = new PDDocument();
int totalPages = sourceDocument.getNumberOfPages(); int totalPages = sourceDocument.getNumberOfPages();
@@ -85,11 +83,7 @@ public class ScalePagesController {
PDPageContentStream contentStream = PDPageContentStream contentStream =
new PDPageContentStream( new PDPageContentStream(
outputDocument, outputDocument, newPage, PDPageContentStream.AppendMode.APPEND, true);
newPage,
PDPageContentStream.AppendMode.APPEND,
true,
true);
float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2; float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2;
float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2; float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2;
@@ -113,7 +107,6 @@ public class ScalePagesController {
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(), baos.toByteArray(),
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "") file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf");
+ "_scaled.pdf");
} }
} }

View File

@@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
@@ -10,7 +11,6 @@ import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -23,7 +23,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -47,10 +46,10 @@ public class SplitPDFController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String pages = request.getPageNumbers(); String pages = request.getPageNumbers();
// open the pdf document // open the pdf document
InputStream inputStream = file.getInputStream();
PDDocument document = PDDocument.load(inputStream);
PDDocument document = Loader.loadPDF(file.getBytes()); List<Integer> pageNumbers = request.getPageNumbersList(document);
List<Integer> pageNumbers = request.getPageNumbersList(document, true);
if (!pageNumbers.contains(document.getNumberOfPages() - 1)) if (!pageNumbers.contains(document.getNumberOfPages() - 1))
pageNumbers.add(document.getNumberOfPages() - 1); pageNumbers.add(document.getNumberOfPages() - 1);
logger.info( logger.info(
@@ -84,9 +83,7 @@ public class SplitPDFController {
Path zipFile = Files.createTempFile("split_documents", ".zip"); Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename = String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file // loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) { for (int i = 0; i < splitDocumentsBoas.size(); i++) {

View File

@@ -9,12 +9,10 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Matrix;
@@ -26,7 +24,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -48,7 +45,7 @@ public class SplitPdfBySectionsController {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
PDDocument sourceDocument = Loader.loadPDF(file.getBytes()); PDDocument sourceDocument = PDDocument.load(file.getInputStream());
// Process the PDF based on split parameters // Process the PDF based on split parameters
int horiz = request.getHorizontalDivisions() + 1; int horiz = request.getHorizontalDivisions() + 1;
@@ -65,9 +62,7 @@ public class SplitPdfBySectionsController {
sourceDocument.close(); sourceDocument.close();
Path zipFile = Files.createTempFile("split_documents", ".zip"); Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename = String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
byte[] data; byte[] data;
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
@@ -120,13 +115,10 @@ public class SplitPdfBySectionsController {
document, document.getPages().indexOf(originalPage)); document, document.getPages().indexOf(originalPage));
try (PDPageContentStream contentStream = try (PDPageContentStream contentStream =
new PDPageContentStream( new PDPageContentStream(subDoc, subPage)) {
subDoc, subPage, AppendMode.APPEND, true, true)) {
// Set clipping area and position // Set clipping area and position
float translateX = -subPageWidth * i; float translateX = -subPageWidth * i;
float translateY = height - subPageHeight * (verticalDivisions - j);
// float translateY = height - subPageHeight * (verticalDivisions - j);
float translateY = -subPageHeight * (verticalDivisions - 1 - j);
contentStream.saveGraphicsState(); contentStream.saveGraphicsState();
contentStream.addRect(0, 0, subPageWidth, subPageHeight); contentStream.addRect(0, 0, subPageWidth, subPageHeight);

View File

@@ -9,7 +9,6 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -20,7 +19,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -44,7 +42,7 @@ public class SplitPdfBySizeController {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
PDDocument sourceDocument = Loader.loadPDF(file.getBytes()); PDDocument sourceDocument = PDDocument.load(file.getInputStream());
// 0 = size, 1 = page count, 2 = doc count // 0 = size, 1 = page count, 2 = doc count
int type = request.getSplitType(); int type = request.getSplitType();
@@ -121,9 +119,7 @@ public class SplitPdfBySizeController {
sourceDocument.close(); sourceDocument.close();
Path zipFile = Files.createTempFile("split_documents", ".zip"); Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename = String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
byte[] data; byte[] data;
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {

View File

@@ -4,7 +4,6 @@ import java.awt.geom.AffineTransform;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.multipdf.LayerUtility; import org.apache.pdfbox.multipdf.LayerUtility;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -41,7 +40,7 @@ public class ToSinglePageController {
throws IOException { throws IOException {
// Load the source document // Load the source document
PDDocument sourceDocument = Loader.loadPDF(request.getFileInput().getBytes()); PDDocument sourceDocument = PDDocument.load(request.getFileInput().getInputStream());
// Calculate total height and max width // Calculate total height and max width
float totalHeight = 0; float totalHeight = 0;

View File

@@ -10,13 +10,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -24,18 +20,13 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.servlet.view.RedirectView;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
import stirling.software.SPDF.model.api.user.UpdateUserDetails;
import stirling.software.SPDF.model.api.user.UsernameAndPass;
@Controller @Controller
@Tag(name = "User", description = "User APIs")
@RequestMapping("/api/v1/user") @RequestMapping("/api/v1/user")
public class UserController { public class UserController {
@@ -43,13 +34,14 @@ public class UserController {
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/register") @PostMapping("/register")
public String register(@ModelAttribute UsernameAndPass requestModel, Model model) { public String register(
if (userService.usernameExists(requestModel.getUsername())) { @RequestParam String username, @RequestParam String password, Model model) {
if (userService.usernameExists(username)) {
model.addAttribute("error", "Username already exists"); model.addAttribute("error", "Username already exists");
return "register"; return "register";
} }
userService.saveUser(requestModel.getUsername(), requestModel.getPassword()); userService.saveUser(username, password);
return "redirect:/login?registered=true"; return "redirect:/login?registered=true";
} }
@@ -57,15 +49,12 @@ public class UserController {
@PostMapping("/change-username-and-password") @PostMapping("/change-username-and-password")
public RedirectView changeUsernameAndPassword( public RedirectView changeUsernameAndPassword(
Principal principal, Principal principal,
@ModelAttribute UpdateUserDetails requestModel, @RequestParam String currentPassword,
@RequestParam String newUsername,
@RequestParam String newPassword,
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
RedirectAttributes redirectAttributes) { RedirectAttributes redirectAttributes) {
String currentPassword = requestModel.getPassword();
String newPassword = requestModel.getNewPassword();
String newUsername = requestModel.getNewUsername();
if (principal == null) { if (principal == null) {
return new RedirectView("/change-creds?messageType=notAuthenticated"); return new RedirectView("/change-creds?messageType=notAuthenticated");
} }
@@ -231,27 +220,11 @@ public class UserController {
if (currentUsername.equals(username)) { if (currentUsername.equals(username)) {
throw new IllegalArgumentException("Cannot delete currently logined in user."); throw new IllegalArgumentException("Cannot delete currently logined in user.");
} }
invalidateUserSessions(username);
userService.deleteUser(username); userService.deleteUser(username);
return "redirect:/addUsers"; return "redirect:/addUsers";
} }
@Autowired private SessionRegistry sessionRegistry;
private void invalidateUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
if (userDetails.getUsername().equals(username)) {
for (SessionInformation session :
sessionRegistry.getAllSessions(principal, false)) {
session.expireNow();
}
}
}
}
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/get-api-key") @PostMapping("/get-api-key")
public ResponseEntity<String> getApiKey(Principal principal) { public ResponseEntity<String> getApiKey(Principal principal) {

View File

@@ -1,69 +0,0 @@
package stirling.software.SPDF.controller.api.converters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.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("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/book/pdf")
@Operation(
summary =
"Convert a BOOK/comic (*.epub | *.mobi | *.azw3 | *.fb2 | *.txt | *.docx) to PDF",
description =
"(Requires bookAndHtmlFormatsInstalled 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 (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException(
"bookAndHtmlFormatsInstalled flag is False, this functionality is not avaiable");
}
if (fileInput == null) {
throw new IllegalArgumentException("Please provide a file for conversion.");
}
String originalFilename = Filenames.toSimpleFileName(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);
}
}

View File

@@ -1,7 +1,5 @@
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;
@@ -9,11 +7,10 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest; import stirling.software.SPDF.model.api.GeneralFile;
import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.FileToPdf;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@@ -22,17 +19,12 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
public class ConvertHtmlToPDF { public class ConvertHtmlToPDF {
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf") @PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
@Operation( @Operation(
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF", summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
description = description =
"This endpoint takes an HTML or ZIP file input and converts it to a PDF format.") "This endpoint takes an HTML or ZIP file input and converts it to a PDF format.")
public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute HTMLToPdfRequest request) public ResponseEntity<byte[]> HtmlToPdf(@ModelAttribute GeneralFile request) throws Exception {
throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
if (fileInput == null) { if (fileInput == null) {
@@ -40,17 +32,12 @@ public class ConvertHtmlToPDF {
"Please provide an HTML or ZIP file for conversion."); "Please provide an HTML or ZIP file for conversion.");
} }
String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename()); String originalFilename = fileInput.getOriginalFilename();
if (originalFilename == null if (originalFilename == null
|| (!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 = byte[] pdfBytes = FileToPdf.convertHtmlToPdf(fileInput.getBytes(), originalFilename);
FileToPdf.convertHtmlToPdf(
request,
fileInput.getBytes(),
originalFilename,
bookAndHtmlFormatsInstalled);
String outputFilename = String outputFilename =
originalFilename.replaceFirst("[.][^.]+$", "") originalFilename.replaceFirst("[.][^.]+$", "")

View File

@@ -18,7 +18,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -55,11 +54,9 @@ public class ConvertImgPDFController {
colorTypeResult = ImageType.BINARY; colorTypeResult = ImageType.BINARY;
} }
// returns bytes for image // returns bytes for image
boolean singleImage = "single".equals(singleOrMultiple); boolean singleImage = singleOrMultiple.equals("single");
byte[] result = null; byte[] result = null;
String filename = String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try { try {
result = result =
PdfUtils.convertFromPdf( PdfUtils.convertFromPdf(
@@ -99,7 +96,7 @@ public class ConvertImgPDFController {
@Operation( @Operation(
summary = "Convert images to a PDF file", summary = "Convert images to a PDF file",
description = description =
"This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:MISO") "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?")
public ResponseEntity<byte[]> convertToPdf(@ModelAttribute ConvertToPdfRequest request) public ResponseEntity<byte[]> convertToPdf(@ModelAttribute ConvertToPdfRequest request)
throws IOException { throws IOException {
MultipartFile[] file = request.getFileInput(); MultipartFile[] file = request.getFileInput();
@@ -116,6 +113,6 @@ public class ConvertImgPDFController {
private String getMediaType(String imageFormat) { private String getMediaType(String imageFormat) {
String mimeType = URLConnection.guessContentTypeFromName("." + imageFormat); String mimeType = URLConnection.guessContentTypeFromName("." + imageFormat);
return "null".equals(mimeType) ? "application/octet-stream" : mimeType; return mimeType.equals("null") ? "application/octet-stream" : mimeType;
} }
} }

View File

@@ -1,17 +1,8 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.util.List;
import java.util.Map;
import org.commonmark.Extension;
import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.node.Node; import org.commonmark.node.Node;
import org.commonmark.parser.Parser; import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.AttributeProvider;
import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.html.HtmlRenderer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -19,7 +10,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -32,15 +22,11 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
public class ConvertMarkdownToPdf { public class ConvertMarkdownToPdf {
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf") @PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
@Operation( @Operation(
summary = "Convert a Markdown file to PDF", summary = "Convert a Markdown file to PDF",
description = description =
"This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format. Input:MARKDOWN Output:PDF Type:SISO") "This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format.")
public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile request) public ResponseEntity<byte[]> markdownToPdf(@ModelAttribute GeneralFile request)
throws Exception { throws Exception {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
@@ -49,30 +35,18 @@ public class ConvertMarkdownToPdf {
throw new IllegalArgumentException("Please provide a Markdown file for conversion."); throw new IllegalArgumentException("Please provide a Markdown file for conversion.");
} }
String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename()); String originalFilename = fileInput.getOriginalFilename();
if (originalFilename == null || !originalFilename.endsWith(".md")) { if (originalFilename == null || !originalFilename.endsWith(".md")) {
throw new IllegalArgumentException("File must be in .md format."); throw new IllegalArgumentException("File must be in .md format.");
} }
// Convert Markdown to HTML using CommonMark // Convert Markdown to HTML using CommonMark
List<Extension> extensions = List.of(TablesExtension.create()); Parser parser = Parser.builder().build();
Parser parser = Parser.builder().extensions(extensions).build();
Node document = parser.parse(new String(fileInput.getBytes())); Node document = parser.parse(new String(fileInput.getBytes()));
HtmlRenderer renderer = HtmlRenderer renderer = HtmlRenderer.builder().build();
HtmlRenderer.builder()
.attributeProviderFactory(context -> new TableAttributeProvider())
.extensions(extensions)
.build();
String htmlContent = renderer.render(document); String htmlContent = renderer.render(document);
byte[] pdfBytes = byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html");
FileToPdf.convertHtmlToPdf(
null,
htmlContent.getBytes(),
"converted.html",
bookAndHtmlFormatsInstalled);
String outputFilename = String outputFilename =
originalFilename.replaceFirst("[.][^.]+$", "") originalFilename.replaceFirst("[.][^.]+$", "")
@@ -80,12 +54,3 @@ public class ConvertMarkdownToPdf {
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
} }
class TableAttributeProvider implements AttributeProvider {
@Override
public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
if (node instanceof TableBlock) {
attributes.put("class", "table table-striped");
}
}
}

View File

@@ -16,7 +16,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -32,7 +31,7 @@ public class ConvertOfficeController {
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
// Check for valid file extension // Check for valid file extension
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename()); String originalFilename = inputFile.getOriginalFilename();
if (originalFilename == null if (originalFilename == null
|| !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) { || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) {
throw new IllegalArgumentException("Invalid file extension"); throw new IllegalArgumentException("Invalid file extension");
@@ -80,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();
@@ -90,8 +89,7 @@ public class ConvertOfficeController {
byte[] pdfByteArray = convertToPdf(inputFile); byte[] pdfByteArray = convertToPdf(inputFile);
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
pdfByteArray, pdfByteArray,
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
.replaceFirst("[.][^.]+$", "")
+ "_convertedToPDF.pdf"); + "_convertedToPDF.pdf");
} }
} }

View File

@@ -1,103 +0,0 @@
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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.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("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@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 bookAndHtmlFormatsInstalled 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 (!bookAndHtmlFormatsInstalled) {
throw new IllegalArgumentException(
"bookAndHtmlFormatsInstalled 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 =
Filenames.toSimpleFileName(fileInput.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "."
+ outputFormat; // Remove file extension and append .pdf
return WebResponseUtils.bytesToWebResponse(outputFileBytes, outputFilename);
}
}

View File

@@ -2,10 +2,6 @@ package stirling.software.SPDF.controller.api.converters;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
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;
@@ -13,7 +9,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -22,7 +17,6 @@ import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest; import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
import stirling.software.SPDF.model.api.converters.PdfToWordRequest; import stirling.software.SPDF.model.api.converters.PdfToWordRequest;
import stirling.software.SPDF.utils.PDFToFile; import stirling.software.SPDF.utils.PDFToFile;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
@@ -65,21 +59,9 @@ public class ConvertPDFToOffice {
throws IOException, InterruptedException { throws IOException, InterruptedException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String outputFormat = request.getOutputFormat(); String outputFormat = request.getOutputFormat();
if ("txt".equals(request.getOutputFormat())) {
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) { PDFToFile pdfToFile = new PDFToFile();
PDFTextStripper stripper = new PDFTextStripper(); return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
String text = stripper.getText(document);
return WebResponseUtils.bytesToWebResponse(
text.getBytes(),
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ ".txt",
MediaType.TEXT_PLAIN);
}
} else {
PDFToFile pdfToFile = new PDFToFile();
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
}
} }
@PostMapping(consumes = "multipart/form-data", value = "/pdf/word") @PostMapping(consumes = "multipart/form-data", value = "/pdf/word")

View File

@@ -12,7 +12,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -64,9 +63,7 @@ public class ConvertPDFToPDFA {
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_PDFA.pdf";
.replaceFirst("[.][^.]+$", "")
+ "_PDFA.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
} }

View File

@@ -6,8 +6,6 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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.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;
@@ -28,10 +26,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RequestMapping("/api/v1/convert") @RequestMapping("/api/v1/convert")
public class ConvertWebsiteToPDF { public class ConvertWebsiteToPDF {
@Autowired
@Qualifier("bookAndHtmlFormatsInstalled")
private boolean bookAndHtmlFormatsInstalled;
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf") @PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
@Operation( @Operation(
summary = "Convert a URL to a PDF", summary = "Convert a URL to a PDF",
@@ -53,11 +47,7 @@ public class ConvertWebsiteToPDF {
// Prepare the OCRmyPDF command // Prepare the OCRmyPDF command
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
if (!bookAndHtmlFormatsInstalled) { command.add("weasyprint");
command.add("weasyprint");
} else {
command.add("wkhtmltopdf");
}
command.add(URL); command.add(URL);
command.add(tempOutputFile.toString()); command.add(tempOutputFile.toString());

View File

@@ -1,10 +1,10 @@
package stirling.software.SPDF.controller.api.converters; package stirling.software.SPDF.controller.api.converters;
import java.io.ByteArrayInputStream;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -44,7 +44,8 @@ public class ExtractController {
ArrayList<String> tableData = new ArrayList<>(); ArrayList<String> tableData = new ArrayList<>();
int columnsCount = 0; int columnsCount = 0;
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) { try (PDDocument document =
PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
final double res = 72; // PDF units are at 72 DPI final double res = 72; // PDF units are at 72 DPI
PDFTableStripper stripper = new PDFTableStripper(); PDFTableStripper stripper = new PDFTableStripper();
PDPage pdPage = document.getPage(form.getPageId() - 1); PDPage pdPage = document.getPage(form.getPageId() - 1);

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api.filters;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
@@ -13,7 +12,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -41,10 +39,10 @@ public class FilterController {
String text = request.getText(); String text = request.getText();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
PDDocument pdfDocument = Loader.loadPDF(inputFile.getBytes()); 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, Filenames.toSimpleFileName(inputFile.getOriginalFilename())); pdfDocument, inputFile.getOriginalFilename());
return null; return null;
} }
@@ -58,10 +56,10 @@ public class FilterController {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String pageNumber = request.getPageNumbers(); String pageNumber = request.getPageNumbers();
PDDocument pdfDocument = Loader.loadPDF(inputFile.getBytes()); PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
if (PdfUtils.hasImages(pdfDocument, pageNumber)) if (PdfUtils.hasImages(pdfDocument, pageNumber))
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename())); pdfDocument, inputFile.getOriginalFilename());
return null; return null;
} }
@@ -75,7 +73,7 @@ public class FilterController {
String pageCount = request.getPageCount(); String pageCount = request.getPageCount();
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = Loader.loadPDF(inputFile.getBytes()); PDDocument document = PDDocument.load(inputFile.getInputStream());
int actualPageCount = document.getNumberOfPages(); int actualPageCount = document.getNumberOfPages();
boolean valid = false; boolean valid = false;
@@ -109,7 +107,7 @@ public class FilterController {
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = Loader.loadPDF(inputFile.getBytes()); PDDocument document = PDDocument.load(inputFile.getInputStream());
PDPage firstPage = document.getPage(0); PDPage firstPage = document.getPage(0);
PDRectangle actualPageSize = firstPage.getMediaBox(); PDRectangle actualPageSize = firstPage.getMediaBox();
@@ -185,7 +183,7 @@ public class FilterController {
String comparator = request.getComparator(); String comparator = request.getComparator();
// Load the PDF // Load the PDF
PDDocument document = Loader.loadPDF(inputFile.getBytes()); 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);

View File

@@ -5,7 +5,6 @@ import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition; import org.apache.pdfbox.text.TextPosition;
@@ -18,7 +17,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -45,7 +43,7 @@ public class AutoRenameController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback(); Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();
PDDocument document = Loader.loadPDF(file.getBytes()); PDDocument document = PDDocument.load(file.getInputStream());
PDFTextStripper reader = PDFTextStripper reader =
new PDFTextStripper() { new PDFTextStripper() {
class LineInfo { class LineInfo {
@@ -134,8 +132,7 @@ public class AutoRenameController {
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf"); return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
} else { } else {
logger.info("File has no good title to be found"); logger.info("File has no good title to be found");
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(document, file.getOriginalFilename());
document, Filenames.toSimpleFileName(file.getOriginalFilename()));
} }
} }
} }

View File

@@ -5,6 +5,7 @@ import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt; import java.awt.image.DataBufferInt;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
@@ -12,7 +13,6 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -31,7 +31,6 @@ import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Result; import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer; import com.google.zxing.common.HybridBinarizer;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -43,8 +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/Stirling-Tools/Stirling-PDF"; private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") @PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
@Operation( @Operation(
@@ -56,7 +54,8 @@ public class AutoSplitPdfController {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean duplexMode = request.isDuplexMode(); boolean duplexMode = request.isDuplexMode();
PDDocument document = Loader.loadPDF(file.getBytes()); InputStream inputStream = file.getInputStream();
PDDocument document = PDDocument.load(inputStream);
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
List<PDDocument> splitDocuments = new ArrayList<>(); List<PDDocument> splitDocuments = new ArrayList<>();
@@ -65,13 +64,12 @@ public class AutoSplitPdfController {
for (int page = 0; page < document.getNumberOfPages(); ++page) { for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150); BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);
String result = decodeQRCode(bim); String result = decodeQRCode(bim);
if ((QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result)) && page != 0) {
if (QR_CONTENT.equals(result) && page != 0) {
splitDocuments.add(new PDDocument()); splitDocuments.add(new PDDocument());
} }
if (!splitDocuments.isEmpty() if (!splitDocuments.isEmpty() && !QR_CONTENT.equals(result)) {
&& !QR_CONTENT.equals(result)
&& !QR_CONTENT_OLD.equals(result)) {
splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page)); splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));
} else if (page == 0) { } else if (page == 0) {
PDDocument firstDocument = new PDDocument(); PDDocument firstDocument = new PDDocument();
@@ -80,7 +78,7 @@ public class AutoSplitPdfController {
} }
// If duplexMode is true and current page is a divider, then skip next page // If duplexMode is true and current page is a divider, then skip next page
if (duplexMode && (QR_CONTENT.equals(result) || QR_CONTENT_OLD.equals(result))) { if (duplexMode && QR_CONTENT.equals(result)) {
page++; page++;
} }
} }
@@ -98,9 +96,7 @@ public class AutoSplitPdfController {
document.close(); document.close();
Path zipFile = Files.createTempFile("split_documents", ".zip"); Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename = String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
byte[] data; byte[] data;
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {

View File

@@ -2,20 +2,22 @@ package stirling.software.SPDF.controller.api.misc;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.apache.pdfbox.Loader; import javax.imageio.ImageIO;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree; import org.apache.pdfbox.pdmodel.PDPageTree;
import org.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.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@@ -24,12 +26,13 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest; import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@@ -37,8 +40,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
public class BlankPageController { public class BlankPageController {
private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class);
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks") @PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
@Operation( @Operation(
summary = "Remove blank pages from a PDF file", summary = "Remove blank pages from a PDF file",
@@ -52,7 +53,7 @@ public class BlankPageController {
PDDocument document = null; PDDocument document = null;
try { try {
document = Loader.loadPDF(inputFile.getBytes()); document = PDDocument.load(inputFile.getInputStream());
PDPageTree pages = document.getDocumentCatalog().getPages(); PDPageTree pages = document.getDocumentCatalog().getPages();
PDFTextStripper textStripper = new PDFTextStripper(); PDFTextStripper textStripper = new PDFTextStripper();
@@ -61,35 +62,56 @@ public class BlankPageController {
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
for (PDPage page : pages) { for (PDPage page : pages) {
logger.info("checking page " + pageIndex); System.out.println("checking page " + pageIndex);
textStripper.setStartPage(pageIndex + 1); textStripper.setStartPage(pageIndex + 1);
textStripper.setEndPage(pageIndex + 1); textStripper.setEndPage(pageIndex + 1);
String pageText = textStripper.getText(document); String pageText = textStripper.getText(document);
boolean hasText = !pageText.trim().isEmpty(); boolean hasText = !pageText.trim().isEmpty();
Boolean blank = false;
if (hasText) { if (hasText) {
logger.info("page " + pageIndex + " has text, not blank"); pagesToKeepIndex.add(pageIndex);
blank = false; System.out.println("page " + pageIndex + " has text");
} else { } else {
boolean hasImages = PdfUtils.hasImagesOnPage(page); boolean hasImages = PdfUtils.hasImagesOnPage(page);
if (hasImages) { if (hasImages) {
logger.info("page " + pageIndex + " has image, running blank detection"); System.out.println("page " + pageIndex + " has image");
Path tempFile = Files.createTempFile("image_", ".png");
// Render image and save as temp file // Render image and save as temp file
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 30); BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 300);
blank = isBlankImage(image, threshold, whitePercent, threshold); ImageIO.write(image, "png", tempFile.toFile());
List<String> command =
new ArrayList<>(
Arrays.asList(
"python3",
System.getProperty("user.dir")
+ "/scripts/detect-blank-pages.py",
tempFile.toString(),
"--threshold",
String.valueOf(threshold),
"--white_percent",
String.valueOf(whitePercent)));
// Run CLI command
ProcessExecutorResult returnCode =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// does contain data
if (returnCode.getRc() == 0) {
System.out.println(
"page " + pageIndex + " has image which is not blank");
pagesToKeepIndex.add(pageIndex);
} else {
System.out.println("Skipping, Image was blank for page #" + pageIndex);
}
} }
} }
if (blank) {
logger.info("Skipping, Image was blank for page #" + pageIndex);
} else {
logger.info("page " + pageIndex + " has image which is not blank");
pagesToKeepIndex.add(pageIndex);
}
pageIndex++; pageIndex++;
} }
System.out.print("pagesToKeep=" + pagesToKeepIndex.size());
// Remove pages not present in pagesToKeepIndex // Remove pages not present in pagesToKeepIndex
List<Integer> pageIndices = List<Integer> pageIndices =
IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList()); IntStream.range(0, pages.getCount()).boxed().collect(Collectors.toList());
@@ -102,8 +124,7 @@ public class BlankPageController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
.replaceFirst("[.][^.]+$", "")
+ "_blanksRemoved.pdf"); + "_blanksRemoved.pdf");
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@@ -112,30 +133,4 @@ public class BlankPageController {
if (document != null) document.close(); if (document != null) document.close();
} }
} }
public static boolean isBlankImage(
BufferedImage image, int threshold, double whitePercent, int blurSize) {
if (image == null) {
logger.info("Error: Image is null");
return false;
}
// Convert to binary image based on the threshold
int whitePixels = 0;
int totalPixels = image.getWidth() * image.getHeight();
for (int i = 0; i < image.getHeight(); i++) {
for (int j = 0; j < image.getWidth(); j++) {
int color = image.getRGB(j, i) & 0xFF;
if (color >= 255 - threshold) {
whitePixels++;
}
}
}
double whitePixelPercentage = (whitePixels / (double) totalPixels) * 100;
logger.info(String.format("Page has white pixel percent of %.2f%%", whitePixelPercentage));
return whitePixelPercentage >= whitePercent;
}
} }

View File

@@ -13,7 +13,6 @@ import java.util.List;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -29,7 +28,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -149,7 +147,7 @@ public class CompressController {
if (expectedOutputSize != null && autoMode) { if (expectedOutputSize != null && autoMode) {
long outputFileSize = Files.size(tempOutputFile); long outputFileSize = Files.size(tempOutputFile);
if (outputFileSize > expectedOutputSize) { if (outputFileSize > expectedOutputSize) {
try (PDDocument doc = Loader.loadPDF(new File(tempOutputFile.toString()))) { try (PDDocument doc = PDDocument.load(new File(tempOutputFile.toString()))) {
long previousFileSize = 0; long previousFileSize = 0;
double scaleFactor = 1.0; double scaleFactor = 1.0;
while (true) { while (true) {
@@ -265,9 +263,7 @@ public class CompressController {
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_Optimized.pdf";
.replaceFirst("[.][^.]+$", "")
+ "_Optimized.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
} }

View File

@@ -1,6 +1,7 @@
package stirling.software.SPDF.controller.api.misc; package stirling.software.SPDF.controller.api.misc;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@@ -16,7 +17,6 @@ import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -74,9 +74,10 @@ public class ExtractImageScansController {
List<String> images = new ArrayList<>(); List<String> images = new ArrayList<>();
// Check if input file is a PDF // Check if input file is a PDF
if ("pdf".equalsIgnoreCase(extension)) { if (extension.equalsIgnoreCase("pdf")) {
// Load PDF document // Load PDF document
try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) { try (PDDocument document =
PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
int pageCount = document.getNumberOfPages(); int pageCount = document.getNumberOfPages();
images = new ArrayList<>(); images = new ArrayList<>();

View File

@@ -14,7 +14,6 @@ import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@@ -29,7 +28,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -55,7 +53,7 @@ public class ExtractImagesController {
System.out.println( System.out.println(
System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format); System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
PDDocument document = Loader.loadPDF(file.getBytes()); PDDocument document = PDDocument.load(file.getBytes());
// Create ByteArrayOutputStream to write zip file to byte array // Create ByteArrayOutputStream to write zip file to byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -67,9 +65,7 @@ public class ExtractImagesController {
zos.setLevel(Deflater.BEST_COMPRESSION); zos.setLevel(Deflater.BEST_COMPRESSION);
int imageIndex = 1; int imageIndex = 1;
String filename = String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
int pageNum = 0; int pageNum = 0;
Set<Integer> processedImages = new HashSet<>(); Set<Integer> processedImages = new HashSet<>();
// Iterate over each page // Iterate over each page
@@ -88,19 +84,19 @@ public class ExtractImagesController {
// Convert image to desired format // Convert image to desired format
RenderedImage renderedImage = image.getImage(); RenderedImage renderedImage = image.getImage();
BufferedImage bufferedImage = null; BufferedImage bufferedImage = null;
if ("png".equalsIgnoreCase(format)) { if (format.equalsIgnoreCase("png")) {
bufferedImage = bufferedImage =
new BufferedImage( new BufferedImage(
renderedImage.getWidth(), renderedImage.getWidth(),
renderedImage.getHeight(), renderedImage.getHeight(),
BufferedImage.TYPE_INT_ARGB); BufferedImage.TYPE_INT_ARGB);
} else if ("jpeg".equalsIgnoreCase(format) || "jpg".equalsIgnoreCase(format)) { } else if (format.equalsIgnoreCase("jpeg") || format.equalsIgnoreCase("jpg")) {
bufferedImage = bufferedImage =
new BufferedImage( new BufferedImage(
renderedImage.getWidth(), renderedImage.getWidth(),
renderedImage.getHeight(), renderedImage.getHeight(),
BufferedImage.TYPE_INT_RGB); BufferedImage.TYPE_INT_RGB);
} else if ("gif".equalsIgnoreCase(format)) { } else if (format.equalsIgnoreCase("gif")) {
bufferedImage = bufferedImage =
new BufferedImage( new BufferedImage(
renderedImage.getWidth(), renderedImage.getWidth(),

View File

@@ -16,7 +16,6 @@ import java.util.Random;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
@@ -29,11 +28,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Hidden;
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;
@@ -50,7 +49,7 @@ public class FakeScanControllerWIP {
// TODO // TODO
@Hidden @Hidden
// @PostMapping(consumes = "multipart/form-data", value = "/fakeScan") @PostMapping(consumes = "multipart/form-data", value = "/fakeScan")
@Operation( @Operation(
summary = "Repair a PDF file", summary = "Repair a PDF file",
description = description =
@@ -58,7 +57,7 @@ public class FakeScanControllerWIP {
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) throws IOException { public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
PDDocument document = Loader.loadPDF(inputFile.getBytes()); PDDocument document = PDDocument.load(inputFile.getBytes());
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);
for (int page = 0; page < document.getNumberOfPages(); ++page) { for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB); BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
@@ -142,9 +141,7 @@ public class FakeScanControllerWIP {
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scanned.pdf";
.replaceFirst("[.][^.]+$", "")
+ "_scanned.pdf";
return WebResponseUtils.boasToWebResponse(baos, outputFilename); return WebResponseUtils.boasToWebResponse(baos, outputFilename);
} }
} }

View File

@@ -7,7 +7,6 @@ import java.util.Calendar;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation; import org.apache.pdfbox.pdmodel.PDDocumentInformation;
@@ -18,7 +17,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -69,7 +67,7 @@ public class MetadataController {
allRequestParams = new java.util.HashMap<String, String>(); allRequestParams = new java.util.HashMap<String, String>();
} }
// Load the PDF file into a PDDocument // Load the PDF file into a PDDocument
PDDocument document = Loader.loadPDF(pdfFile.getBytes()); PDDocument document = PDDocument.load(pdfFile.getBytes());
// Get the document information from the PDF // Get the document information from the PDF
PDDocumentInformation info = document.getDocumentInformation(); PDDocumentInformation info = document.getDocumentInformation();
@@ -110,15 +108,15 @@ public class MetadataController {
for (Entry<String, String> entry : allRequestParams.entrySet()) { for (Entry<String, String> entry : allRequestParams.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
// Check if the key is a standard metadata key // Check if the key is a standard metadata key
if (!"Author".equalsIgnoreCase(key) if (!key.equalsIgnoreCase("Author")
&& !"CreationDate".equalsIgnoreCase(key) && !key.equalsIgnoreCase("CreationDate")
&& !"Creator".equalsIgnoreCase(key) && !key.equalsIgnoreCase("Creator")
&& !"Keywords".equalsIgnoreCase(key) && !key.equalsIgnoreCase("Keywords")
&& !"modificationDate".equalsIgnoreCase(key) && !key.equalsIgnoreCase("modificationDate")
&& !"Producer".equalsIgnoreCase(key) && !key.equalsIgnoreCase("Producer")
&& !"Subject".equalsIgnoreCase(key) && !key.equalsIgnoreCase("Subject")
&& !"Title".equalsIgnoreCase(key) && !key.equalsIgnoreCase("Title")
&& !"Trapped".equalsIgnoreCase(key) && !key.equalsIgnoreCase("Trapped")
&& !key.contains("customKey") && !key.contains("customKey")
&& !key.contains("customValue")) { && !key.contains("customValue")) {
info.setCustomMetadataValue(key, entry.getValue()); info.setCustomMetadataValue(key, entry.getValue());
@@ -165,8 +163,6 @@ public class MetadataController {
document.setDocumentInformation(info); document.setDocumentInformation(info);
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_metadata.pdf");
.replaceFirst("[.][^.]+$", "")
+ "_metadata.pdf");
} }
} }

View File

@@ -24,7 +24,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -41,7 +40,7 @@ public class OCRController {
private static final Logger logger = LoggerFactory.getLogger(OCRController.class); private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
public List<String> getAvailableTesseractLanguages() { public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tessdata"; String tessdataDir = "/usr/share/tesseract-ocr/5/tessdata";
File[] files = new File(tessdataDir).listFiles(); File[] files = new File(tessdataDir).listFiles();
if (files == null) { if (files == null) {
return Collections.emptyList(); return Collections.emptyList();
@@ -75,7 +74,7 @@ public class OCRController {
throw new IOException("Please select at least one language."); throw new IOException("Please select at least one language.");
} }
if (!"hocr".equals(ocrRenderType) && !"sandwich".equals(ocrRenderType)) { if (!ocrRenderType.equals("hocr") && !ocrRenderType.equals("sandwich")) {
throw new IOException("ocrRenderType wrong"); throw new IOException("ocrRenderType wrong");
} }
@@ -128,7 +127,7 @@ public class OCRController {
if (cleanFinal != null && cleanFinal) { if (cleanFinal != null && cleanFinal) {
command.add("--clean-final"); command.add("--clean-final");
} }
if (ocrType != null && !"".equals(ocrType)) { if (ocrType != null && !ocrType.equals("")) {
if ("skip-text".equals(ocrType)) { if ("skip-text".equals(ocrType)) {
command.add("--skip-text"); command.add("--skip-text");
} else if ("force-ocr".equals(ocrType)) { } else if ("force-ocr".equals(ocrType)) {
@@ -183,16 +182,12 @@ public class OCRController {
// Return the OCR processed PDF as a response // Return the OCR processed PDF as a response
String outputFilename = String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.pdf";
.replaceFirst("[.][^.]+$", "")
+ "_OCR.pdf";
if (sidecar != null && sidecar) { if (sidecar != null && sidecar) {
// Create a zip file containing both the PDF and the text file // Create a zip file containing both the PDF and the text file
String outputZipFilename = String outputZipFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_OCR.zip";
.replaceFirst("[.][^.]+$", "")
+ "_OCR.zip";
Path tempZipFile = Files.createTempFile("output_", ".zip"); Path tempZipFile = Files.createTempFile("output_", ".zip");
try (ZipOutputStream zipOut = try (ZipOutputStream zipOut =

View File

@@ -12,7 +12,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -31,7 +30,7 @@ public class OverlayImageController {
@Operation( @Operation(
summary = "Overlay image onto a PDF file", summary = "Overlay image onto a PDF file",
description = description =
"This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:SISO") "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO")
public ResponseEntity<byte[]> overlayImage(@ModelAttribute OverlayImageRequest request) { public ResponseEntity<byte[]> overlayImage(@ModelAttribute OverlayImageRequest request) {
MultipartFile pdfFile = request.getFileInput(); MultipartFile pdfFile = request.getFileInput();
MultipartFile imageFile = request.getImageFile(); MultipartFile imageFile = request.getImageFile();
@@ -45,9 +44,7 @@ public class OverlayImageController {
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
result, result,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_overlayed.pdf");
.replaceFirst("[.][^.]+$", "")
+ "_overlayed.pdf");
} catch (IOException e) { } catch (IOException e) {
logger.error("Failed to add image to PDF", e); logger.error("Failed to add image to PDF", e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST); return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

View File

@@ -4,13 +4,11 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.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.apache.pdfbox.pdmodel.font.Standard14Fonts;
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;
@@ -21,7 +19,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -51,7 +48,7 @@ public class PageNumbersController {
String customText = request.getCustomText(); String customText = request.getCustomText();
int pageNumber = startingNumber; int pageNumber = startingNumber;
byte[] fileBytes = file.getBytes(); byte[] fileBytes = file.getBytes();
PDDocument document = Loader.loadPDF(fileBytes); PDDocument document = PDDocument.load(fileBytes);
float marginFactor; float marginFactor;
switch (customMargin.toLowerCase()) { switch (customMargin.toLowerCase()) {
@@ -74,6 +71,7 @@ public class PageNumbersController {
} }
float fontSize = 12.0f; float fontSize = 12.0f;
PDType1Font font = PDType1Font.HELVETICA;
if (pagesToNumber == null || pagesToNumber.length() == 0) { if (pagesToNumber == null || pagesToNumber.length() == 0) {
pagesToNumber = "all"; pagesToNumber = "all";
} }
@@ -94,7 +92,7 @@ public class PageNumbersController {
.replace("{total}", String.valueOf(document.getNumberOfPages())) .replace("{total}", String.valueOf(document.getNumberOfPages()))
.replace( .replace(
"{filename}", "{filename}",
Filenames.toSimpleFileName(file.getOriginalFilename()) file.getOriginalFilename()
.replaceFirst("[.][^.]+$", "")) .replaceFirst("[.][^.]+$", ""))
: String.valueOf(pageNumber); : String.valueOf(pageNumber);
@@ -129,9 +127,9 @@ public class PageNumbersController {
PDPageContentStream contentStream = PDPageContentStream contentStream =
new PDPageContentStream( new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true); document, page, PDPageContentStream.AppendMode.APPEND, true);
contentStream.beginText(); contentStream.beginText();
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), fontSize); contentStream.setFont(font, fontSize);
contentStream.newLineAtOffset(x, y); contentStream.newLineAtOffset(x, y);
contentStream.showText(text); contentStream.showText(text);
contentStream.endText(); contentStream.endText();
@@ -146,8 +144,7 @@ public class PageNumbersController {
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
baos.toByteArray(), baos.toByteArray(),
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "") file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf",
+ "_numbersAdded.pdf",
MediaType.APPLICATION_PDF); MediaType.APPLICATION_PDF);
} }
} }

View File

@@ -15,7 +15,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -66,9 +65,7 @@ public class RepairController {
// Return the optimized PDF as a response // Return the optimized PDF as a response
String outputFilename = String outputFilename =
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_repaired.pdf";
.replaceFirst("[.][^.]+$", "")
+ "_repaired.pdf";
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
} }
} }

View File

@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api.misc;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript; import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
@@ -16,7 +15,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -38,7 +36,7 @@ public class ShowJavascript {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
String script = ""; String script = "";
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) { try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
if (document.getDocumentCatalog() != null if (document.getDocumentCatalog() != null
&& document.getDocumentCatalog().getNames() != null) { && document.getDocumentCatalog().getNames() != null) {
@@ -55,8 +53,7 @@ public class ShowJavascript {
script += script +=
"// File: " "// File: "
+ Filenames.toSimpleFileName( + inputFile.getOriginalFilename()
inputFile.getOriginalFilename())
+ ", Script: " + ", Script: "
+ name + name
+ "\n" + "\n"
@@ -68,14 +65,12 @@ public class ShowJavascript {
if (script.isEmpty()) { if (script.isEmpty()) {
script = script =
"PDF '" "PDF '" + inputFile.getOriginalFilename() + "' does not contain Javascript";
+ Filenames.toSimpleFileName(inputFile.getOriginalFilename())
+ "' does not contain Javascript";
} }
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
script.getBytes(StandardCharsets.UTF_8), script.getBytes(StandardCharsets.UTF_8),
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + ".js"); inputFile.getOriginalFilename() + ".js");
} }
} }
} }

View File

@@ -1,320 +0,0 @@
package stirling.software.SPDF.controller.api.misc;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.List;
import javax.imageio.ImageIO;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.AddStampRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class StampController {
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
@Operation(
summary = "Add stamp to a PDF file",
description =
"This endpoint adds a stamp to a given PDF file. Users can specify the stamp type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> addStamp(@ModelAttribute AddStampRequest request)
throws IOException, Exception {
MultipartFile pdfFile = request.getFileInput();
String stampType = request.getStampType();
String stampText = request.getStampText();
MultipartFile stampImage = request.getStampImage();
String alphabet = request.getAlphabet();
float fontSize = request.getFontSize();
float rotation = request.getRotation();
float opacity = request.getOpacity();
int position = request.getPosition(); // Updated to use 1-9 positioning logic
float overrideX = request.getOverrideX(); // New field for X override
float overrideY = request.getOverrideY(); // New field for Y override
String customColor = request.getCustomColor();
float marginFactor;
switch (request.getCustomMargin().toLowerCase()) {
case "small":
marginFactor = 0.02f;
break;
case "medium":
marginFactor = 0.035f;
break;
case "large":
marginFactor = 0.05f;
break;
case "x-large":
marginFactor = 0.075f;
break;
default:
marginFactor = 0.035f;
break;
}
// Load the input PDF
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
for (int pageIndex : pageNumbers) {
int zeroBasedIndex = pageIndex - 1;
if (zeroBasedIndex >= 0 && zeroBasedIndex < document.getNumberOfPages()) {
PDPage page = document.getPage(zeroBasedIndex);
PDRectangle pageSize = page.getMediaBox();
float margin = marginFactor * (pageSize.getWidth() + pageSize.getHeight()) / 2;
PDPageContentStream contentStream =
new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(opacity);
contentStream.setGraphicsStateParameters(graphicsState);
if ("text".equalsIgnoreCase(stampType)) {
addTextStamp(
contentStream,
stampText,
document,
page,
rotation,
position,
fontSize,
alphabet,
overrideX,
overrideY,
margin,
customColor);
} else if ("image".equalsIgnoreCase(stampType)) {
addImageStamp(
contentStream,
stampImage,
document,
page,
rotation,
position,
fontSize,
overrideX,
overrideY,
margin);
}
contentStream.close();
}
}
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
.replaceFirst("[.][^.]+$", "")
+ "_stamped.pdf");
}
private void addTextStamp(
PDPageContentStream contentStream,
String stampText,
PDDocument document,
PDPage page,
float rotation,
int position, // 1-9 positioning logic
float fontSize,
String alphabet,
float overrideX, // X override
float overrideY,
float margin,
String colorString) // Y override
throws IOException {
String resourceDir = "";
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
switch (alphabet) {
case "arabic":
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
break;
case "japanese":
resourceDir = "static/fonts/Meiryo.ttf";
break;
case "korean":
resourceDir = "static/fonts/malgun.ttf";
break;
case "chinese":
resourceDir = "static/fonts/SimSun.ttf";
break;
case "roman":
default:
resourceDir = "static/fonts/NotoSans-Regular.ttf";
break;
}
if (!"".equals(resourceDir)) {
ClassPathResource classPathResource = new ClassPathResource(resourceDir);
String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
File tempFile = Files.createTempFile("NotoSansFont", fileExtension).toFile();
try (InputStream is = classPathResource.getInputStream();
FileOutputStream os = new FileOutputStream(tempFile)) {
IOUtils.copy(is, os);
}
font = PDType0Font.load(document, tempFile);
tempFile.deleteOnExit();
}
contentStream.setFont(font, fontSize);
Color redactColor;
try {
if (!colorString.startsWith("#")) {
colorString = "#" + colorString;
}
redactColor = Color.decode(colorString);
} catch (NumberFormatException e) {
redactColor = Color.LIGHT_GRAY;
}
contentStream.setNonStrokingColor(redactColor);
PDRectangle pageSize = page.getMediaBox();
float x, y;
if (overrideX >= 0 && overrideY >= 0) {
// Use override values if provided
x = overrideX;
y = overrideY;
} else {
x = calculatePositionX(pageSize, position, fontSize, font, fontSize, stampText, margin);
y =
calculatePositionY(
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
}
contentStream.beginText();
contentStream.setTextMatrix(Matrix.getRotateInstance(Math.toRadians(rotation), x, y));
contentStream.showText(stampText);
contentStream.endText();
}
private void addImageStamp(
PDPageContentStream contentStream,
MultipartFile stampImage,
PDDocument document,
PDPage page,
float rotation,
int position, // 1-9 positioning logic
float fontSize,
float overrideX,
float overrideY,
float margin)
throws IOException {
// Load the stamp image
BufferedImage image = ImageIO.read(stampImage.getInputStream());
// Compute width based on original aspect ratio
float aspectRatio = (float) image.getWidth() / (float) image.getHeight();
// Desired physical height (in PDF points)
float desiredPhysicalHeight = fontSize;
// Desired physical width based on the aspect ratio
float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio;
// Convert the BufferedImage to PDImageXObject
PDImageXObject xobject = LosslessFactory.createFromImage(document, image);
PDRectangle pageSize = page.getMediaBox();
float x, y;
if (overrideX >= 0 && overrideY >= 0) {
// Use override values if provided
x = overrideX;
y = overrideY;
} else {
x = calculatePositionX(pageSize, position, desiredPhysicalWidth, null, 0, null, margin);
y = calculatePositionY(pageSize, position, fontSize, margin);
}
contentStream.saveGraphicsState();
contentStream.transform(Matrix.getTranslateInstance(x, y));
contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));
contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight);
contentStream.restoreGraphicsState();
}
private float calculatePositionX(
PDRectangle pageSize,
int position,
float contentWidth,
PDFont font,
float fontSize,
String text,
float margin)
throws IOException {
float actualWidth =
(text != null) ? calculateTextWidth(text, font, fontSize) : contentWidth;
switch (position % 3) {
case 1: // Left
return pageSize.getLowerLeftX() + margin;
case 2: // Center
return (pageSize.getWidth() - actualWidth) / 2;
case 0: // Right
return pageSize.getUpperRightX() - actualWidth - margin;
default:
return 0;
}
}
private float calculatePositionY(
PDRectangle pageSize, int position, float height, float margin) {
switch ((position - 1) / 3) {
case 0: // Top
return pageSize.getUpperRightY() - height - margin;
case 1: // Middle
return (pageSize.getHeight() - height) / 2;
case 2: // Bottom
return pageSize.getLowerLeftY() + margin;
default:
return 0;
}
}
private float calculateTextWidth(String text, PDFont font, float fontSize) throws IOException {
return font.getStringWidth(text) / 1000 * fontSize;
}
private float calculateTextCapHeight(PDFont font, float fontSize) {
return font.getFontDescriptor().getCapHeight() / 1000 * fontSize;
}
}

View File

@@ -1,8 +1,6 @@
package stirling.software.SPDF.controller.api.pipeline; package stirling.software.SPDF.controller.api.pipeline;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -41,57 +39,6 @@ public class ApiDocService {
return "http://localhost:" + port + contextPath + "/v1/api-docs"; return "http://localhost:" + port + contextPath + "/v1/api-docs";
} }
Map<String, List<String>> outputToFileTypes = new HashMap<>();
public List getExtensionTypes(boolean output, String operationName) {
if (outputToFileTypes.size() == 0) {
outputToFileTypes.put("PDF", Arrays.asList("pdf"));
outputToFileTypes.put(
"IMAGE",
Arrays.asList(
"png", "jpg", "jpeg", "gif", "webp", "bmp", "tif", "tiff", "svg", "psd",
"ai", "eps"));
outputToFileTypes.put(
"ZIP",
Arrays.asList("zip", "rar", "7z", "tar", "gz", "bz2", "xz", "lz", "lzma", "z"));
outputToFileTypes.put("WORD", Arrays.asList("doc", "docx", "odt", "rtf"));
outputToFileTypes.put("CSV", Arrays.asList("csv"));
outputToFileTypes.put("JS", Arrays.asList("js", "jsx"));
outputToFileTypes.put("HTML", Arrays.asList("html", "htm", "xhtml"));
outputToFileTypes.put("JSON", Arrays.asList("json"));
outputToFileTypes.put("TXT", Arrays.asList("txt", "text", "md", "markdown"));
outputToFileTypes.put("PPT", Arrays.asList("ppt", "pptx", "odp"));
outputToFileTypes.put("XML", Arrays.asList("xml", "xsd", "xsl"));
outputToFileTypes.put(
"BOOK", Arrays.asList("epub", "mobi", "azw3", "fb2", "txt", "docx"));
// type.
}
if (apiDocsJsonRootNode == null || apiDocumentation.size() == 0) {
loadApiDocumentation();
}
if (!apiDocumentation.containsKey(operationName)) {
return null;
}
ApiEndpoint endpoint = apiDocumentation.get(operationName);
String description = endpoint.getDescription();
Pattern pattern = null;
if (output) {
pattern = Pattern.compile("Output:(\\w+)");
} else {
pattern = Pattern.compile("Input:(\\w+)");
}
Matcher matcher = pattern.matcher(description);
while (matcher.find()) {
String type = matcher.group(1).toUpperCase();
if (outputToFileTypes.containsKey(type)) {
return outputToFileTypes.get(type);
}
}
return null;
}
@Autowired(required = false) @Autowired(required = false)
private UserServiceInterface userService; private UserServiceInterface userService;

View File

@@ -2,9 +2,7 @@ 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.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@@ -86,26 +84,9 @@ public class PipelineController {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zipOut = new ZipOutputStream(baos); ZipOutputStream zipOut = new ZipOutputStream(baos);
// A map to keep track of filenames and their counts
Map<String, Integer> filenameCount = new HashMap<>();
// Loop through each file and add it to the zip // Loop through each file and add it to the zip
for (Resource file : outputFiles) { for (Resource file : outputFiles) {
String originalFilename = file.getFilename(); ZipEntry zipEntry = new ZipEntry(file.getFilename());
String filename = originalFilename;
// Check if the filename already exists, and modify it if necessary
if (filenameCount.containsKey(originalFilename)) {
int count = filenameCount.get(originalFilename);
String baseName = originalFilename.replaceAll("\\.[^.]*$", "");
String extension = originalFilename.replaceAll("^.*\\.", "");
filename = baseName + "(" + count + ")." + extension;
filenameCount.put(originalFilename, count + 1);
} else {
filenameCount.put(originalFilename, 1);
}
ZipEntry zipEntry = new ZipEntry(filename);
zipOut.putNextEntry(zipEntry); zipOut.putNextEntry(zipEntry);
// Read the file into a byte array // Read the file into a byte array

View File

@@ -5,13 +5,10 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -36,9 +33,6 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import io.github.pixee.security.Filenames;
import io.github.pixee.security.ZipSecurity;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import stirling.software.SPDF.SPdfApplication; import stirling.software.SPDF.SPdfApplication;
import stirling.software.SPDF.model.PipelineConfig; import stirling.software.SPDF.model.PipelineConfig;
@@ -86,11 +80,15 @@ public class PipelineProcessor {
operation, operation,
isMultiInputOperation); isMultiInputOperation);
Map<String, Object> parameters = pipelineOperation.getParameters(); Map<String, Object> parameters = pipelineOperation.getParameters();
List<String> inputFileTypes = apiDocService.getExtensionTypes(false, operation); String inputFileExtension = "";
if (inputFileTypes == null) {
inputFileTypes = new ArrayList<String>(Arrays.asList("ALL")); // TODO
} // if (operationNode.has("inputFileType")) {
// List outputFileTypes = apiDocService.getExtensionTypes(true, operation); // inputFileExtension = operationNode.get("inputFileType").asText();
// } else {
inputFileExtension = ".pdf";
// }
final String finalInputFileExtension = inputFileExtension;
String url = getBaseUrl() + operation; String url = getBaseUrl() + operation;
@@ -98,63 +96,55 @@ public class PipelineProcessor {
if (!isMultiInputOperation) { if (!isMultiInputOperation) {
for (Resource file : outputFiles) { for (Resource file : outputFiles) {
boolean hasInputFileType = false; boolean hasInputFileType = false;
for (String extension : inputFileTypes) { if (file.getFilename().endsWith(inputFileExtension)) {
if ("ALL".equals(extension) || file.getFilename().endsWith(extension)) { hasInputFileType = true;
hasInputFileType = true; MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("fileInput", file);
body.add("fileInput", file);
for (Entry<String, Object> entry : parameters.entrySet()) { for (Entry<String, Object> entry : parameters.entrySet()) {
body.add(entry.getKey(), entry.getValue()); body.add(entry.getKey(), entry.getValue());
}
ResponseEntity<byte[]> response = sendWebRequest(url, body);
// If the operation is filter and the response body is null or empty,
// skip
// this
// file
if (operation.startsWith("filter-")
&& (response.getBody() == null
|| response.getBody().length == 0)) {
logger.info("Skipping file due to failing {}", operation);
continue;
}
if (!response.getStatusCode().equals(HttpStatus.OK)) {
logPrintStream.println("Error: " + response.getBody());
hasErrors = true;
continue;
}
processOutputFiles(operation, response, newOutputFiles);
} }
ResponseEntity<byte[]> response = sendWebRequest(url, body);
// If the operation is filter and the response body is null or empty, skip
// this
// file
if (operation.startsWith("filter-")
&& (response.getBody() == null || response.getBody().length == 0)) {
logger.info("Skipping file due to failing {}", operation);
continue;
}
if (!response.getStatusCode().equals(HttpStatus.OK)) {
logPrintStream.println("Error: " + response.getBody());
hasErrors = true;
continue;
}
processOutputFiles(operation, file.getFilename(), response, newOutputFiles);
} }
if (!hasInputFileType) { if (!hasInputFileType) {
logPrintStream.println( logPrintStream.println(
"No files with extension " "No files with extension "
+ String.join(", ", inputFileTypes) + inputFileExtension
+ " found for operation " + " found for operation "
+ operation); + operation);
hasErrors = true; hasErrors = true;
} }
outputFiles = newOutputFiles;
} }
} else { } else {
// Filter and collect all files that match the inputFileExtension // Filter and collect all files that match the inputFileExtension
List<Resource> matchingFiles; List<Resource> matchingFiles =
if (inputFileTypes.contains("ALL")) { outputFiles.stream()
matchingFiles = new ArrayList<>(outputFiles); .filter(
} else { file ->
final List<String> finalinputFileTypes = inputFileTypes; file.getFilename()
matchingFiles = .endsWith(finalInputFileExtension))
outputFiles.stream() .collect(Collectors.toList());
.filter(
file ->
finalinputFileTypes.stream()
.anyMatch(file.getFilename()::endsWith))
.collect(Collectors.toList());
}
// Check if there are matching files // Check if there are matching files
if (!matchingFiles.isEmpty()) { if (!matchingFiles.isEmpty()) {
@@ -174,7 +164,11 @@ public class PipelineProcessor {
// Handle the response // Handle the response
if (response.getStatusCode().equals(HttpStatus.OK)) { if (response.getStatusCode().equals(HttpStatus.OK)) {
processOutputFiles(operation, response, newOutputFiles); processOutputFiles(
operation,
matchingFiles.get(0).getFilename(),
response,
newOutputFiles);
} else { } else {
// Log error if the response status is not OK // Log error if the response status is not OK
logPrintStream.println( logPrintStream.println(
@@ -184,19 +178,17 @@ public class PipelineProcessor {
} else { } else {
logPrintStream.println( logPrintStream.println(
"No files with extension " "No files with extension "
+ String.join(", ", inputFileTypes) + inputFileExtension
+ " found for multi-input operation " + " found for multi-input operation "
+ operation); + operation);
hasErrors = true; hasErrors = true;
} }
} }
logPrintStream.close(); logPrintStream.close();
outputFiles = newOutputFiles;
} }
if (hasErrors) { if (hasErrors) {
logger.error("Errors occurred during processing. Log: {}", logStream.toString()); logger.error("Errors occurred during processing. Log: {}", logStream.toString());
} }
return outputFiles; return outputFiles;
} }
@@ -204,7 +196,6 @@ 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);
@@ -217,41 +208,22 @@ public class PipelineProcessor {
return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class); return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);
} }
public static String removeTrailingNaming(String filename) {
// Splitting filename into name and extension
int dotIndex = filename.lastIndexOf(".");
if (dotIndex == -1) {
// No extension found
return filename;
}
String name = filename.substring(0, dotIndex);
String extension = filename.substring(dotIndex);
// Finding the last underscore
int underscoreIndex = name.lastIndexOf("_");
if (underscoreIndex == -1) {
// No underscore found
return filename;
}
// Removing the last part and reattaching the extension
return name.substring(0, underscoreIndex) + extension;
}
private List<Resource> processOutputFiles( private List<Resource> processOutputFiles(
String operation, ResponseEntity<byte[]> response, List<Resource> newOutputFiles) String operation,
String fileName,
ResponseEntity<byte[]> response,
List<Resource> newOutputFiles)
throws IOException { throws IOException {
// Define filename // Define filename
String newFilename; String newFilename;
if (operation.contains("auto-rename")) { if ("auto-rename".equals(operation)) {
// 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 = removeTrailingNaming(extractFilename(response)); newFilename = fileName;
} }
// Check if the response body is a zip file // Check if the response body is a zip file
@@ -272,28 +244,6 @@ 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");
@@ -336,7 +286,7 @@ public class PipelineProcessor {
new ByteArrayResource(file.getBytes()) { new ByteArrayResource(file.getBytes()) {
@Override @Override
public String getFilename() { public String getFilename() {
return Filenames.toSimpleFileName(file.getOriginalFilename()); return file.getOriginalFilename();
} }
}; };
outputFiles.add(fileResource); outputFiles.add(fileResource);
@@ -359,7 +309,7 @@ public class PipelineProcessor {
List<Resource> unzippedFiles = new ArrayList<>(); List<Resource> unzippedFiles = new ArrayList<>();
try (ByteArrayInputStream bais = new ByteArrayInputStream(data); try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ZipInputStream zis = ZipSecurity.createHardenedInputStream(bais)) { ZipInputStream zis = new ZipInputStream(bais)) {
ZipEntry entry; ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) { while ((entry = zis.getNextEntry()) != null) {

View File

@@ -4,35 +4,44 @@ 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.io.OutputStream; import java.security.KeyFactory;
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.pdfbox.Loader; 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.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
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.openssl.PEMDecryptorProvider; import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.util.io.pem.PemReader;
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;
@@ -42,7 +51,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -60,22 +68,11 @@ public class CertSignController {
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
class CreateSignature extends CreateSignatureBase {
public CreateSignature(KeyStore keystore, char[] pin)
throws KeyStoreException,
UnrecoverableKeyException,
NoSuchAlgorithmException,
IOException,
CertificateException {
super(keystore, pin);
}
}
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign") @PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
@Operation( @Operation(
summary = "Sign PDF with a Digital Certificate", summary = "Sign PDF with a Digital Certificate",
description = description =
"This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO") "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO")
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
throws Exception { throws Exception {
MultipartFile pdf = request.getFileInput(); MultipartFile pdf = request.getFileInput();
@@ -83,7 +80,6 @@ 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();
@@ -91,96 +87,203 @@ public class CertSignController {
String name = request.getName(); String name = request.getName();
Integer pageNumber = request.getPageNumber(); Integer pageNumber = request.getPageNumber();
if (certType == null) { PrivateKey privateKey = null;
throw new IllegalArgumentException("Cert type must be provided"); X509Certificate cert = null;
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());
KeyStore ks = null; // Load the PDF
try (PDDocument document = PDDocument.load(pdf.getBytes())) {
logger.info("Successfully loaded the provided PDF");
SignatureOptions signatureOptions = new SignatureOptions();
switch (certType) { // If you want to show the signature
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);
}
// TODO: page number // ATTEMPT 2
if (showSignature != null && showSignature) {
PDPage page = document.getPage(pageNumber - 1);
CreateSignature createSignature = new CreateSignature(ks, password.toCharArray()); PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); if (acroForm == null) {
sign(pdf.getBytes(), baos, createSignature, name, location, reason); acroForm = new PDAcroForm(document);
return WebResponseUtils.boasToWebResponse( document.getDocumentCatalog().setAcroForm(acroForm);
baos, }
Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "")
+ "_signed.pdf");
}
private static void sign( // Create a new signature field and widget
byte[] input,
OutputStream output,
CreateSignature instance,
String name,
String location,
String reason) {
try (PDDocument doc = Loader.loadPDF(input)) {
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName(name);
signature.setLocation(location);
signature.setReason(reason);
signature.setSignDate(Calendar.getInstance());
doc.addSignature(signature, instance); PDSignatureField signatureField = new PDSignatureField(acroForm);
doc.saveIncremental(output); PDAnnotationWidget widget = signatureField.getWidgets().get(0);
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 PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password) private byte[] parsePEM(byte[] content) throws IOException {
throws IOException, OperatorCreationException, PKCSException { PemReader pemReader =
try (PEMParser pemParser = new PemReader(new InputStreamReader(new ByteArrayInputStream(content)));
new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) { return pemReader.readPemObject().getContent();
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 Certificate getCertificateFromPEM(byte[] pemBytes) private boolean isPEM(byte[] content) {
throws IOException, CertificateException { String contentStr = new String(content);
try (ByteArrayInputStream bis = new ByteArrayInputStream(pemBytes)) { return contentStr.contains("-----BEGIN") && contentStr.contains("-----END");
return CertificateFactory.getInstance("X.509").generateCertificate(bis);
}
} }
} }

View File

@@ -11,9 +11,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.cos.COSDocument;
import org.apache.pdfbox.cos.COSInputStream; import org.apache.pdfbox.cos.COSInputStream;
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
@@ -85,7 +87,7 @@ public class GetInfoOnPDF {
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO") @Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException { public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
MultipartFile inputFile = request.getFileInput(); MultipartFile inputFile = request.getFileInput();
try (PDDocument pdfBoxDoc = Loader.loadPDF(inputFile.getBytes()); ) { try (PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream()); ) {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
ObjectNode jsonOutput = objectMapper.createObjectNode(); ObjectNode jsonOutput = objectMapper.createObjectNode();
@@ -127,6 +129,17 @@ public class GetInfoOnPDF {
boolean hasCompression = false; boolean hasCompression = false;
String compressionType = "None"; String compressionType = "None";
COSDocument cosDoc = pdfBoxDoc.getDocument();
for (COSObject cosObject : cosDoc.getObjects()) {
if (cosObject.getObject() instanceof COSStream) {
COSStream cosStream = (COSStream) cosObject.getObject();
if (COSName.OBJ_STM.equals(cosStream.getItem(COSName.TYPE))) {
hasCompression = true;
compressionType = "Object Streams";
break;
}
}
}
basicInfo.put("Compression", hasCompression); basicInfo.put("Compression", hasCompression);
if (hasCompression) basicInfo.put("CompressionType", compressionType); if (hasCompression) basicInfo.put("CompressionType", compressionType);
@@ -330,6 +343,7 @@ public class GetInfoOnPDF {
permissionsNode.put("CanModify", ap.canModify()); permissionsNode.put("CanModify", ap.canModify());
permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations()); permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations());
permissionsNode.put("CanPrint", ap.canPrint()); permissionsNode.put("CanPrint", ap.canPrint());
permissionsNode.put("CanPrintDegraded", ap.canPrintDegraded());
encryption.set( encryption.set(
"Permissions", permissionsNode); // set the node under "Permissions" "Permissions", permissionsNode); // set the node under "Permissions"

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api.security;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission; import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy; import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
@@ -15,7 +14,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -40,12 +38,11 @@ public class PasswordController {
MultipartFile fileInput = request.getFileInput(); MultipartFile fileInput = request.getFileInput();
String password = request.getPassword(); String password = request.getPassword();
PDDocument document = Loader.loadPDF(fileInput.getBytes(), password); PDDocument document = PDDocument.load(fileInput.getBytes(), password);
document.setAllSecurityToBeRemoved(true); document.setAllSecurityToBeRemoved(true);
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename()) fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")
.replaceFirst("[.][^.]+$", "")
+ "_password_removed.pdf"); + "_password_removed.pdf");
} }
@@ -69,7 +66,7 @@ public class PasswordController {
boolean canPrint = request.isCanPrint(); boolean canPrint = request.isCanPrint();
boolean canPrintFaithful = request.isCanPrintFaithful(); boolean canPrintFaithful = request.isCanPrintFaithful();
PDDocument document = Loader.loadPDF(fileInput.getBytes()); PDDocument document = PDDocument.load(fileInput.getBytes());
AccessPermission ap = new AccessPermission(); AccessPermission ap = new AccessPermission();
ap.setCanAssembleDocument(!canAssembleDocument); ap.setCanAssembleDocument(!canAssembleDocument);
ap.setCanExtractContent(!canExtractContent); ap.setCanExtractContent(!canExtractContent);
@@ -90,13 +87,10 @@ public class PasswordController {
if ("".equals(ownerPassword) && "".equals(password)) if ("".equals(ownerPassword) && "".equals(password))
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename()) fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "")
.replaceFirst("[.][^.]+$", "")
+ "_permissions.pdf"); + "_permissions.pdf");
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(fileInput.getOriginalFilename()) fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf");
.replaceFirst("[.][^.]+$", "")
+ "_passworded.pdf");
} }
} }

View File

@@ -2,15 +2,14 @@ package stirling.software.SPDF.controller.api.security;
import java.awt.Color; import java.awt.Color;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
@@ -25,7 +24,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -59,7 +57,7 @@ public class RedactController {
System.out.println(listOfTextString); System.out.println(listOfTextString);
String[] listOfText = listOfTextString.split("\n"); String[] listOfText = listOfTextString.split("\n");
byte[] bytes = file.getBytes(); byte[] bytes = file.getBytes();
PDDocument document = Loader.loadPDF(bytes); PDDocument document = PDDocument.load(new ByteArrayInputStream(bytes));
Color redactColor; Color redactColor;
try { try {
@@ -88,9 +86,7 @@ public class RedactController {
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight())); PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
imageDocument.addPage(newPage); imageDocument.addPage(newPage);
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim); PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
PDPageContentStream contentStream = PDPageContentStream contentStream = new PDPageContentStream(imageDocument, newPage);
new PDPageContentStream(
imageDocument, newPage, AppendMode.APPEND, true, true);
contentStream.drawImage(pdImage, 0, 0); contentStream.drawImage(pdImage, 0, 0);
contentStream.close(); contentStream.close();
} }
@@ -105,8 +101,7 @@ public class RedactController {
byte[] pdfContent = baos.toByteArray(); byte[] pdfContent = baos.toByteArray();
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
pdfContent, pdfContent,
Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst("[.][^.]+$", "") file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_redacted.pdf");
+ "_redacted.pdf");
} }
private void redactFoundText( private void redactFoundText(

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF.controller.api.security;
import java.io.IOException; import java.io.IOException;
import org.apache.pdfbox.Loader;
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;
@@ -28,7 +27,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -54,7 +52,7 @@ public class SanitizeController {
boolean removeLinks = request.isRemoveLinks(); boolean removeLinks = request.isRemoveLinks();
boolean removeFonts = request.isRemoveFonts(); boolean removeFonts = request.isRemoveFonts();
try (PDDocument document = Loader.loadPDF(inputFile.getBytes())) { try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
if (removeJavaScript) { if (removeJavaScript) {
sanitizeJavaScript(document); sanitizeJavaScript(document);
} }
@@ -77,8 +75,7 @@ public class SanitizeController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(inputFile.getOriginalFilename()) inputFile.getOriginalFilename().replaceFirst("[.][^.]+$", "")
.replaceFirst("[.][^.]+$", "")
+ "_sanitized.pdf"); + "_sanitized.pdf");
} }
} }

View File

@@ -6,19 +6,16 @@ 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.nio.file.Files;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
@@ -31,7 +28,6 @@ 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.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -62,7 +58,7 @@ public class WatermarkController {
int heightSpacer = request.getHeightSpacer(); int heightSpacer = request.getHeightSpacer();
// Load the input PDF // Load the input PDF
PDDocument document = Loader.loadPDF(pdfFile.getBytes()); PDDocument document = PDDocument.load(pdfFile.getInputStream());
// Create a page in the document // Create a page in the document
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {
@@ -70,14 +66,14 @@ public class WatermarkController {
// Get the page's content stream // Get the page's content stream
PDPageContentStream contentStream = PDPageContentStream contentStream =
new PDPageContentStream( new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true); document, page, PDPageContentStream.AppendMode.APPEND, true);
// Set transparency // Set transparency
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState(); PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(opacity); graphicsState.setNonStrokingAlphaConstant(opacity);
contentStream.setGraphicsStateParameters(graphicsState); contentStream.setGraphicsStateParameters(graphicsState);
if ("text".equalsIgnoreCase(watermarkType)) { if (watermarkType.equalsIgnoreCase("text")) {
addTextWatermark( addTextWatermark(
contentStream, contentStream,
watermarkText, watermarkText,
@@ -88,7 +84,7 @@ public class WatermarkController {
heightSpacer, heightSpacer,
fontSize, fontSize,
alphabet); alphabet);
} else if ("image".equalsIgnoreCase(watermarkType)) { } else if (watermarkType.equalsIgnoreCase("image")) {
addImageWatermark( addImageWatermark(
contentStream, contentStream,
watermarkImage, watermarkImage,
@@ -106,9 +102,7 @@ public class WatermarkController {
return WebResponseUtils.pdfDocToWebResponse( return WebResponseUtils.pdfDocToWebResponse(
document, document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename()) pdfFile.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_watermarked.pdf");
.replaceFirst("[.][^.]+$", "")
+ "_watermarked.pdf");
} }
private void addTextWatermark( private void addTextWatermark(
@@ -123,7 +117,7 @@ public class WatermarkController {
String alphabet) String alphabet)
throws IOException { throws IOException {
String resourceDir = ""; String resourceDir = "";
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA); PDFont font = PDType1Font.HELVETICA_BOLD;
switch (alphabet) { switch (alphabet) {
case "arabic": case "arabic":
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf"; resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
@@ -143,10 +137,10 @@ public class WatermarkController {
break; break;
} }
if (!"".equals(resourceDir)) { if (!resourceDir.equals("")) {
ClassPathResource classPathResource = new ClassPathResource(resourceDir); ClassPathResource classPathResource = new ClassPathResource(resourceDir);
String fileExtension = resourceDir.substring(resourceDir.lastIndexOf(".")); String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
File tempFile = Files.createTempFile("NotoSansFont", fileExtension).toFile(); File tempFile = File.createTempFile("NotoSansFont", fileExtension);
try (InputStream is = classPathResource.getInputStream(); try (InputStream is = classPathResource.getInputStream();
FileOutputStream os = new FileOutputStream(tempFile)) { FileOutputStream os = new FileOutputStream(tempFile)) {
IOUtils.copy(is, os); IOUtils.copy(is, os);
@@ -159,16 +153,9 @@ public class WatermarkController {
contentStream.setFont(font, fontSize); contentStream.setFont(font, fontSize);
contentStream.setNonStrokingColor(Color.LIGHT_GRAY); contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
String[] textLines = watermarkText.split("\\\\n");
float maxLineWidth = 0;
for (int i = 0; i < textLines.length; ++i) {
maxLineWidth = Math.max(maxLineWidth, font.getStringWidth(textLines[i]));
}
// Set size and location of text watermark // Set size and location of text watermark
float watermarkWidth = widthSpacer + maxLineWidth * fontSize / 1000; float watermarkWidth = widthSpacer + font.getStringWidth(watermarkText) * fontSize / 1000;
float watermarkHeight = heightSpacer + fontSize * textLines.length; float watermarkHeight = heightSpacer + fontSize;
float pageWidth = page.getMediaBox().getWidth(); float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight(); float pageHeight = page.getMediaBox().getHeight();
int watermarkRows = (int) (pageHeight / watermarkHeight + 1); int watermarkRows = (int) (pageHeight / watermarkHeight + 1);
@@ -183,12 +170,7 @@ public class WatermarkController {
(float) Math.toRadians(rotation), (float) Math.toRadians(rotation),
j * watermarkWidth, j * watermarkWidth,
i * watermarkHeight)); i * watermarkHeight));
contentStream.showText(watermarkText);
for (int k = 0; k < textLines.length; ++k) {
contentStream.showText(textLines[k]);
contentStream.newLineAtOffset(0, -fontSize);
}
contentStream.endText(); contentStream.endText();
} }
} }

View File

@@ -33,8 +33,6 @@ public class AccountWebController {
return "redirect:/"; return "redirect:/";
} }
model.addAttribute("currentPage", "login");
if (request.getParameter("error") != null) { if (request.getParameter("error") != null) {
model.addAttribute("error", request.getParameter("error")); model.addAttribute("error", request.getParameter("error"));
@@ -114,7 +112,6 @@ public class AccountWebController {
model.addAttribute("role", user.get().getRolesAsString()); model.addAttribute("role", user.get().getRolesAsString());
model.addAttribute("settings", settingsJson); model.addAttribute("settings", settingsJson);
model.addAttribute("changeCredsFlag", user.get().isFirstLogin()); model.addAttribute("changeCredsFlag", user.get().isFirstLogin());
model.addAttribute("currentPage", "account");
} }
} else { } else {
return "redirect:/"; return "redirect:/";

View File

@@ -1,6 +1,5 @@
package stirling.software.SPDF.controller.web; package stirling.software.SPDF.controller.web;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
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;
@@ -13,22 +12,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
@Tag(name = "Convert", description = "Convert APIs") @Tag(name = "Convert", description = "Convert APIs")
public class ConverterWebController { public class ConverterWebController {
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/book-to-pdf")
@Hidden
public String convertBookToPdfForm(Model model) {
model.addAttribute("currentPage", "book-to-pdf");
return "convert/book-to-pdf";
}
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/pdf-to-book")
@Hidden
public String convertPdfToBookForm(Model model) {
model.addAttribute("currentPage", "pdf-to-book");
return "convert/pdf-to-book";
}
@GetMapping("/img-to-pdf") @GetMapping("/img-to-pdf")
@Hidden @Hidden
public String convertImgToPdfForm(Model model) { public String convertImgToPdfForm(Model model) {

View File

@@ -59,14 +59,6 @@ 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);
@@ -168,6 +160,14 @@ public class GeneralWebController {
return "sign"; return "sign";
} }
@GetMapping("/add-elements")
@Hidden
public String addElements(Model model) {
model.addAttribute("currentPage", "add-elements");
model.addAttribute("fonts", getFontNames());
return "add-elements";
}
@GetMapping("/multi-page-layout") @GetMapping("/multi-page-layout")
@Hidden @Hidden
public String multiPageLayoutForm(Model model) { public String multiPageLayoutForm(Model model) {

Some files were not shown because too many files have changed in this diff Show More