Merge branch 'main' into ru_translation_update
This commit is contained in:
commit
12293f5297
255 changed files with 13645 additions and 4670 deletions
6
.github/workflows/push-docker.yml
vendored
6
.github/workflows/push-docker.yml
vendored
|
@ -21,6 +21,8 @@ jobs:
|
||||||
|
|
||||||
|
|
||||||
- uses: gradle/gradle-build-action@v2.4.2
|
- uses: gradle/gradle-build-action@v2.4.2
|
||||||
|
env:
|
||||||
|
DOCKER_ENABLE_SECURITY: false
|
||||||
with:
|
with:
|
||||||
gradle-version: 7.6
|
gradle-version: 7.6
|
||||||
arguments: clean build
|
arguments: clean build
|
||||||
|
@ -77,6 +79,8 @@ jobs:
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args:
|
||||||
|
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
platforms: linux/amd64,linux/arm64/v8
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,6 +109,8 @@ jobs:
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
tags: ${{ steps.meta2.outputs.tags }}
|
tags: ${{ steps.meta2.outputs.tags }}
|
||||||
labels: ${{ steps.meta2.outputs.labels }}
|
labels: ${{ steps.meta2.outputs.labels }}
|
||||||
|
build-args:
|
||||||
|
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
platforms: linux/amd64,linux/arm64/v8
|
||||||
|
|
||||||
|
|
||||||
|
|
22
.github/workflows/releaseArtifacts.yml
vendored
22
.github/workflows/releaseArtifacts.yml
vendored
|
@ -1,10 +1,20 @@
|
||||||
name: Release Artifacts
|
name: Release Artifacts
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
enable_security: [true, false]
|
||||||
|
include:
|
||||||
|
- enable_security: true
|
||||||
|
file_suffix: '-with-login'
|
||||||
|
- enable_security: false
|
||||||
|
file_suffix: ''
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.2
|
- uses: actions/checkout@v3.5.2
|
||||||
|
|
||||||
|
@ -17,15 +27,17 @@ jobs:
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Generate jar
|
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
||||||
run: ./gradlew clean createExe
|
run: ./gradlew clean createExe
|
||||||
|
env:
|
||||||
|
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
||||||
|
|
||||||
- name: Upload binaries to release
|
- name: Upload binaries to release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
file: ./build/launch4j/Stirling-PDF.exe
|
file: ./build/launch4j/Stirling-PDF.exe
|
||||||
asset_name: Stirling-PDF.exe
|
asset_name: Stirling-PDF${{ matrix.file_suffix }}.exe
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
|
@ -33,13 +45,11 @@ jobs:
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
|
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
|
||||||
|
|
||||||
- name: Upload binaries to release
|
- name: Upload jar binaries to release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
file: ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar
|
file: ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar
|
||||||
asset_name: Stirling-PDF.jar
|
asset_name: Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
|
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -19,7 +19,7 @@ pipeline/
|
||||||
|
|
||||||
#### Stirling-PDF Files ###
|
#### Stirling-PDF Files ###
|
||||||
customFiles/
|
customFiles/
|
||||||
config/
|
configs/
|
||||||
watchedFolders/
|
watchedFolders/
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ watchedFolders/
|
||||||
*.zip
|
*.zip
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.rar
|
*.rar
|
||||||
|
*.db
|
||||||
/build
|
/build
|
||||||
|
|
||||||
/.vscode
|
/.vscode
|
51
Dockerfile
51
Dockerfile
|
@ -1,28 +1,45 @@
|
||||||
# Build jbig2enc in a separate stage
|
# Use the base image
|
||||||
FROM frooodle/stirling-pdf-base:beta4
|
FROM frooodle/stirling-pdf-base:beta4
|
||||||
|
|
||||||
# Create scripts folder and copy local scripts
|
ARG VERSION_TAG
|
||||||
RUN mkdir /scripts
|
|
||||||
COPY ./scripts/* /scripts/
|
|
||||||
|
|
||||||
#Install fonts
|
# Set Environment Variables
|
||||||
RUN mkdir /usr/share/fonts/opentype/noto/
|
ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
|
HOME=/home/stirlingpdfuser \
|
||||||
|
VERSION_TAG=$VERSION_TAG
|
||||||
|
# PUID=1000 \
|
||||||
|
# PGID=1000 \
|
||||||
|
# UMASK=022 \
|
||||||
|
|
||||||
|
|
||||||
|
# Create user and group
|
||||||
|
##RUN groupadd -g $PGID stirlingpdfgroup && \
|
||||||
|
## useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \
|
||||||
|
## mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
|
||||||
|
|
||||||
|
# Set up necessary directories and permissions
|
||||||
|
RUN mkdir -p /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
|
||||||
|
|
||||||
|
# Copy necessary files
|
||||||
|
COPY ./scripts/* /scripts/
|
||||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
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 src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
||||||
RUN fc-cache -f -v
|
|
||||||
|
|
||||||
# Copy the application JAR file
|
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
# Expose the application port
|
# Set font cache and permissions
|
||||||
|
RUN fc-cache -f -v && chmod +x /scripts/init.sh
|
||||||
|
|
||||||
|
##&& \
|
||||||
|
## chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
||||||
|
## chmod +x /scripts/init.sh
|
||||||
|
|
||||||
|
# Expose necessary ports
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Set environment variables
|
# Set user and run command
|
||||||
ENV APP_HOME_NAME="Stirling PDF"
|
##USER stirlingpdfuser
|
||||||
#ENV APP_HOME_DESCRIPTION="Personal PDF Website!"
|
|
||||||
#ENV APP_NAVBAR_NAME="Stirling PDF"
|
|
||||||
|
|
||||||
# Run the application
|
|
||||||
RUN chmod +x /scripts/init.sh
|
|
||||||
ENTRYPOINT ["/scripts/init.sh"]
|
ENTRYPOINT ["/scripts/init.sh"]
|
||||||
CMD ["java", "-jar", "/app.jar"]
|
CMD ["java", "-jar", "/app.jar"]
|
||||||
|
|
|
@ -10,14 +10,45 @@ RUN apt-get update && \
|
||||||
unoconv && \
|
unoconv && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Copy the application JAR file
|
|
||||||
|
# Set Environment Variables
|
||||||
|
ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
|
HOME=/home/stirlingpdfuser \
|
||||||
|
VERSION_TAG=$VERSION_TAG
|
||||||
|
# PUID=1000 \
|
||||||
|
# PGID=1000 \
|
||||||
|
# UMASK=022 \
|
||||||
|
|
||||||
|
# Create user and group
|
||||||
|
#RUN groupadd -g $PGID stirlingpdfgroup && \
|
||||||
|
# useradd -u $PUID -g stirlingpdfgroup -s /bin/sh stirlingpdfuser && \
|
||||||
|
# mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
|
||||||
|
|
||||||
|
# Set up necessary directories and permissions
|
||||||
|
RUN mkdir -p /scripts /usr/share/fonts/opentype/noto /configs /customFiles
|
||||||
|
|
||||||
|
# chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/fonts/opentype/noto /configs /customFiles
|
||||||
|
|
||||||
|
# Copy necessary files
|
||||||
|
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
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
# Set font cache and permissions
|
||||||
|
RUN fc-cache -f -v
|
||||||
|
# chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Expose the application port
|
# Expose the application port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV GROUPS_TO_REMOVE=Python,OpenCV,OCRmyPDF
|
ENV ENDPOINTS_GROUPS_TO_REMOVE=Python,OpenCV,OCRmyPDF
|
||||||
|
ENV DOCKER_ENABLE_SECURITY=false
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
|
#USER stirlingpdfuser
|
||||||
|
|
||||||
CMD ["java", "-jar", "/app.jar"]
|
CMD ["java", "-jar", "/app.jar"]
|
||||||
|
|
|
@ -1,14 +1,34 @@
|
||||||
# Build jbig2enc in a separate stage
|
# Build jbig2enc in a separate stage
|
||||||
FROM bellsoft/liberica-openjdk-alpine:17
|
FROM bellsoft/liberica-openjdk-alpine:17
|
||||||
|
|
||||||
# Copy the application JAR file
|
# Set Environment Variables
|
||||||
|
ENV PUID=1000 \
|
||||||
|
PGID=1000 \
|
||||||
|
UMASK=022 \
|
||||||
|
DOCKER_ENABLE_SECURITY=false \
|
||||||
|
HOME=/home/stirlingpdfuser \
|
||||||
|
VERSION_TAG=$VERSION_TAG
|
||||||
|
|
||||||
|
# Create user and group using Alpine's addgroup and adduser
|
||||||
|
RUN addgroup -g $PGID stirlingpdfgroup && \
|
||||||
|
adduser -u $PUID -G stirlingpdfgroup -s /bin/sh -D stirlingpdfuser && \
|
||||||
|
mkdir -p $HOME && chown stirlingpdfuser:stirlingpdfgroup $HOME
|
||||||
|
|
||||||
|
# Set up necessary directories and permissions
|
||||||
|
RUN mkdir -p /scripts /configs /customFiles && \
|
||||||
|
chown -R stirlingpdfuser:stirlingpdfgroup /scripts /configs /customFiles
|
||||||
|
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
# Set font cache and permissions
|
||||||
|
RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
# Expose the application port
|
# Expose the application port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV GROUPS_TO_REMOVE=CLI
|
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||||
|
ENV DOCKER_ENABLE_SECURITY=false
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["java", "-jar", "/app.jar"]
|
CMD ["java", "-jar", "/app.jar"]
|
||||||
|
|
|
@ -8,7 +8,7 @@ Fork Stirling-PDF and make a new branch out of Main
|
||||||
|
|
||||||
Then add reference to the language in the navbar by adding a new language entry to the dropdown
|
Then add reference to the language in the navbar by adding a new language entry to the dropdown
|
||||||
|
|
||||||
https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/templates/fragments/navbar.html#L306
|
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/Frooodle/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/)
|
||||||
|
@ -25,7 +25,7 @@ The data-language-code is the code used to reference the file in the next step.
|
||||||
|
|
||||||
Start by copying the existing english property file
|
Start by copying the existing english property file
|
||||||
|
|
||||||
[https://github.com/Frooodle/Stirling-PDF/tree/langSetup/src/main/resources/messages_en_GB.properties](https://github.com/Frooodle/Stirling-PDF/blob/main/src/main/resources/messages_en_US.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
|
||||||
|
|
||||||
|
|
221
README.md
221
README.md
|
@ -19,46 +19,66 @@ Any file which has been downloaded by the user will have already been deleted fr
|
||||||
|
|
||||||
Feel free to request any features or bug fixes either in github issues or our [Discord](https://discord.gg/Cn8pWhQRxZ)
|
Feel free to request any features or bug fixes either in github issues or our [Discord](https://discord.gg/Cn8pWhQRxZ)
|
||||||
|
|
||||||
|
|
||||||
![stirling-home](images/stirling-home.png)
|
![stirling-home](images/stirling-home.png)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages.
|
|
||||||
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
|
|
||||||
- Merge multiple PDFs together into a single resultant file
|
|
||||||
- Convert PDFs to and from images
|
|
||||||
- Reorganize PDF pages into different orders.
|
|
||||||
- Add/Generate signatures
|
|
||||||
- Format PDFs into a multi-paged page
|
|
||||||
- Scale page contents size by set %
|
|
||||||
- Adjust Contrast
|
|
||||||
- Crop PDF
|
|
||||||
- Auto Split PDF (With physically scanned page dividers)
|
|
||||||
- Flatten PDFs
|
|
||||||
- Repair PDFs
|
|
||||||
- Detect and remove blank pages
|
|
||||||
- Compare 2 PDFs and show differences in text
|
|
||||||
- Add images to PDFs
|
|
||||||
- Rotating PDFs in 90 degree increments.
|
|
||||||
- Compressing PDFs to decrease their filesize. (Using OCRMyPDF)
|
|
||||||
- Add and remove passwords
|
|
||||||
- Set PDF Permissions
|
|
||||||
- Add watermark(s)
|
|
||||||
- Convert Any common file to PDF (using LibreOffice)
|
|
||||||
- Convert PDF to Word/Powerpoint/Others (using LibreOffice)
|
|
||||||
- Convert HTML to PDF
|
|
||||||
- URL to PDF
|
|
||||||
- Extract images from PDF
|
|
||||||
- Extract images from Scans
|
|
||||||
- Add page numbers
|
|
||||||
- Auto rename file by detecting PDF header text
|
|
||||||
- OCR on PDF (Using OCRMyPDF)
|
|
||||||
- PDF/A conversion (Using OCRMyPDF)
|
|
||||||
- Edit metadata
|
|
||||||
- Dark mode support.
|
- Dark mode support.
|
||||||
- Custom download options (see [here](https://github.com/Frooodle/Stirling-PDF/blob/main/images/settings.png) for example)
|
- Custom download options (see [here](https://github.com/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/Frooodle/Stirling-PDF/tree/main#login-authentication) for documentation)
|
||||||
|
|
||||||
|
|
||||||
|
## **PDF Features**
|
||||||
|
|
||||||
|
### **Page Operations**
|
||||||
|
- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages.
|
||||||
|
- Merge multiple PDFs together into a single resultant file.
|
||||||
|
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files.
|
||||||
|
- Reorganize PDF pages into different orders.
|
||||||
|
- Rotate PDFs in 90-degree increments.
|
||||||
|
- Remove pages.
|
||||||
|
- Multi-page layout (Format PDFs into a multi-paged page).
|
||||||
|
- Scale page contents size by set %.
|
||||||
|
- Adjust Contrast.
|
||||||
|
- Crop PDF.
|
||||||
|
- Auto Split PDF (With physically scanned page dividers).
|
||||||
|
- Extract page(s).
|
||||||
|
- Convert PDF to a single page.
|
||||||
|
|
||||||
|
### **Conversion Operations**
|
||||||
|
- Convert PDFs to and from images.
|
||||||
|
- Convert any common file to PDF (using LibreOffice).
|
||||||
|
- Convert PDF to Word/Powerpoint/Others (using LibreOffice).
|
||||||
|
- Convert HTML to PDF.
|
||||||
|
- URL to PDF.
|
||||||
|
- Markdown to PDF.
|
||||||
|
|
||||||
|
### **Security & Permissions**
|
||||||
|
- Add and remove passwords.
|
||||||
|
- Change/set PDF Permissions.
|
||||||
|
- Add watermark(s).
|
||||||
|
- Certify/sign PDFs.
|
||||||
|
- Sanitize PDFs.
|
||||||
|
- Auto-redact text.
|
||||||
|
|
||||||
|
### **Other Operations**
|
||||||
|
- Add/Generate/Write signatures.
|
||||||
|
- Repair PDFs.
|
||||||
|
- Detect and remove blank pages.
|
||||||
|
- Compare 2 PDFs and show differences in text.
|
||||||
|
- Add images to PDFs.
|
||||||
|
- Compress PDFs to decrease their filesize (Using OCRMyPDF).
|
||||||
|
- Extract images from PDF.
|
||||||
|
- Extract images from Scans.
|
||||||
|
- Add page numbers.
|
||||||
|
- Auto rename file by detecting PDF header text.
|
||||||
|
- OCR on PDF (Using OCRMyPDF).
|
||||||
|
- PDF/A conversion (Using OCRMyPDF).
|
||||||
|
- Edit metadata.
|
||||||
|
- Flatten PDFs.
|
||||||
|
- 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 [groups.md](https://github.com/Frooodle/Stirling-PDF/blob/main/Groups.md)
|
For a overview of the tasks and the technology each uses please view [groups.md](https://github.com/Frooodle/Stirling-PDF/blob/main/Groups.md)
|
||||||
Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) hosted by the team at adminforge.de
|
Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) hosted by the team at adminforge.de
|
||||||
|
@ -66,7 +86,6 @@ Hosted instance/demo of the app can be seen [here](https://pdf.adminforge.de/) h
|
||||||
## Technologies used
|
## Technologies used
|
||||||
- Spring Boot + Thymeleaf
|
- Spring Boot + Thymeleaf
|
||||||
- PDFBox
|
- PDFBox
|
||||||
- IText7
|
|
||||||
- [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
|
||||||
|
@ -82,9 +101,9 @@ Please view https://github.com/Frooodle/Stirling-PDF/blob/main/LocalRunGuide.md
|
||||||
### Docker
|
### Docker
|
||||||
https://hub.docker.com/r/frooodle/s-pdf
|
https://hub.docker.com/r/frooodle/s-pdf
|
||||||
|
|
||||||
Stirling PDF has 3 different versions, a Full version, Lite and ultra-Lite. Depending on the types of features you use you may want a smaller image to save on space.
|
Stirling PDF has 3 different versions, a Full version, Lite, and ultra-Lite. Depending on the types of features you use you may want a smaller image to save on space.
|
||||||
To see what the different versions offer please look at our [version mapping](https://github.com/Frooodle/Stirling-PDF/blob/main/Version-groups.md)
|
To see what the different versions offer please look at our [version mapping](https://github.com/Frooodle/Stirling-PDF/blob/main/Version-groups.md)
|
||||||
For people that dont mind about space optimisation just use 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)
|
||||||
|
@ -94,19 +113,15 @@ Docker Run
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-v /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata \
|
-v /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata \
|
||||||
|
-v /location/of/extraConfigs:/configs \
|
||||||
|
-e DOCKER_ENABLE_SECURITY=false \
|
||||||
--name stirling-pdf \
|
--name stirling-pdf \
|
||||||
frooodle/s-pdf:latest
|
frooodle/s-pdf:latest
|
||||||
|
|
||||||
|
|
||||||
Can also add these for customisation but are not required
|
Can also add these for customisation but are not required
|
||||||
-v /location/of/extraConfigs:/configs \
|
|
||||||
-v /location/of/customFiles:/customFiles \
|
-v /location/of/customFiles:/customFiles \
|
||||||
-e APP_HOME_NAME="Stirling PDF" \
|
|
||||||
-e APP_HOME_DESCRIPTION="Your locally hosted one-stop-shop for all your PDF needs." \
|
|
||||||
-e APP_NAVBAR_NAME="Stirling PDF" \
|
|
||||||
-e ALLOW_GOOGLE_VISIBILITY="true" \
|
|
||||||
-e APP_ROOT_PATH="/" \
|
|
||||||
-e APP_LOCALE="en_GB" \
|
|
||||||
```
|
```
|
||||||
Docker Compose
|
Docker Compose
|
||||||
```
|
```
|
||||||
|
@ -118,16 +133,10 @@ services:
|
||||||
- '8080:8080'
|
- '8080:8080'
|
||||||
volumes:
|
volumes:
|
||||||
- /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata #Required for extra OCR languages
|
- /location/of/trainingData:/usr/share/tesseract-ocr/4.00/tessdata #Required for extra OCR languages
|
||||||
# - /location/of/extraConfigs:/configs
|
- /location/of/extraConfigs:/configs
|
||||||
# - /location/of/customFiles:/customFiles/
|
# - /location/of/customFiles:/customFiles/
|
||||||
# environment:
|
environment:
|
||||||
# APP_LOCALE: en_GB
|
- DOCKER_ENABLE_SECURITY=false
|
||||||
# APP_HOME_NAME: Stirling PDF
|
|
||||||
# APP_HOME_DESCRIPTION: Your locally hosted one-stop-shop for all your PDF needs.
|
|
||||||
# APP_NAVBAR_NAME: Stirling PDF
|
|
||||||
# APP_ROOT_PATH: /
|
|
||||||
# ALLOW_GOOGLE_VISIBILITY: true
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,8 +144,9 @@ services:
|
||||||
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
|
Please view https://github.com/Frooodle/Stirling-PDF/blob/main/HowToUseOCR.md
|
||||||
|
|
||||||
## Want to add your own language?
|
## Want to add your own language?
|
||||||
Stirling PDF currently supports 16!
|
Stirling PDF currently supports 18!
|
||||||
- English (English) (en_GB)
|
- English (English) (en_GB)
|
||||||
|
- 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)
|
||||||
|
@ -152,54 +162,107 @@ Stirling PDF currently supports 16!
|
||||||
- Russian (Русский) (ru_RU)
|
- Russian (Русский) (ru_RU)
|
||||||
- Basque (Euskara) (eu_ES)
|
- Basque (Euskara) (eu_ES)
|
||||||
- Japanese (日本語) (ja_JP)
|
- Japanese (日本語) (ja_JP)
|
||||||
|
- Dutch (Nederlands) (nl_NL)
|
||||||
|
|
||||||
If you want to add your own language to Stirling-PDF please refer
|
If you want to add your own language to Stirling-PDF please refer
|
||||||
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
|
https://github.com/Frooodle/Stirling-PDF/blob/main/HowToAddNewLanguage.md
|
||||||
|
|
||||||
And please create a PR to merge it back in so others can use it!
|
And please create a PR to merge it back in so others can use it!
|
||||||
|
|
||||||
Also please note as i add new features i will google translate existing languages so that they dont lose support. This could mean that new features need grammer corrections as added.
|
|
||||||
|
|
||||||
## How to View
|
## How to View
|
||||||
1. Open a web browser and navigate to `http://localhost:8080/`
|
1. Open a web browser and navigate to `http://localhost:8080/`
|
||||||
2. Use the application by following the instructions on the website.
|
2. Use the application by following the instructions on the website.
|
||||||
|
|
||||||
|
|
||||||
## Customize App
|
## Customisation
|
||||||
Stirling PDF allows easy customization of the visible application name.
|
Stirling PDF allows easy customization of the app.
|
||||||
Simply use environment variables APP_HOME_NAME, APP_HOME_DESCRIPTION and APP_NAVBAR_NAME with Docker or Java.
|
Includes things like
|
||||||
If running Java directly, you can also pass these as properties using -D arguments.
|
- Custom application name
|
||||||
|
- Custom slogans, icons, images, and even custom HTML (via file overrides)
|
||||||
|
|
||||||
Using the same method you can also change
|
|
||||||
|
|
||||||
- The default language by providing APP_LOCALE with values like de-DE fr-FR or ar-AR (Note the - character not _ ) to select your default language (Will always default to English on invalid locale) Current accepted locales can be seen above in the Want to add your own language section
|
There are two options for this, either using the generated settings file ``settings.yml``
|
||||||
- Enable/Disable search engine visiblility with ALLOW_GOOGLE_VISIBILITY with true / false values. Default disable visiblility.
|
This file is located in the ``/configs`` directory and follows standard YAML formatting
|
||||||
- Change root URI for Stirling-PDF ie change server.com/ to server.com/pdf-app by running APP_ROOT_PATH as pdf-app
|
|
||||||
- Disable and remove endpoints and functionality from Stirling-PDF. Currently the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma seperated 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/groups.md)
|
Environment variables are also supported and would override the settings file
|
||||||
- Change the max file size allowed through the server with the environment variable MAX_FILE_SIZE. default 2000MB
|
For example in the settings.yml you have
|
||||||
- Customise static files such as app logo by placing files in the /customFiles/static/ directory. Example to customise 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
|
```
|
||||||
- Enable/Disable metric api endpoints with ENABLE_API_METRICS. Default enabled
|
system:
|
||||||
|
defaultLocale: 'en-US'
|
||||||
|
```
|
||||||
|
|
||||||
|
To have this via an environment variable you would have ``SYSTEM_DEFAULTLOCALE``
|
||||||
|
|
||||||
|
The Current list of settings is
|
||||||
|
```
|
||||||
|
security:
|
||||||
|
enableLogin: false # set to 'true' to enable login
|
||||||
|
csrfDisabled: true
|
||||||
|
|
||||||
|
system:
|
||||||
|
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||||
|
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||||
|
customStaticFilePath: '/customFiles/static/' # Directory path for custom static files
|
||||||
|
|
||||||
|
#ui:
|
||||||
|
# appName: exampleAppName # Application's visible name
|
||||||
|
# homeDescription: I am a description # Short description or tagline shown on homepage.
|
||||||
|
# appNameNavbar: navbarName # Name displayed on the navigation bar
|
||||||
|
|
||||||
|
endpoints:
|
||||||
|
toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
||||||
|
groupsToRemove: [] # List groups to disable (e.g. ['LibreOffice'])
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
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
|
||||||
|
- 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/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
|
||||||
|
|
||||||
|
### Environment only parameters
|
||||||
|
- ``SYSTEM_ROOTURIPATH`` ie set to ``/pdf-app`` to Set the application's root URI to ``localhost:8080/pdf-app``
|
||||||
|
- ``SYSTEM_CONNECTIONTIMEOUTMINUTES`` to set custom connection timeout values
|
||||||
|
- ``DOCKER_ENABLE_SECURITY`` to tell docker to download security jar (required as true for auth login)
|
||||||
|
|
||||||
## API
|
## API
|
||||||
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation
|
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation
|
||||||
[here](https://app.swaggerhub.com/apis-docs/Frooodle/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation (Or by following the API button in your settings of Stirling-PDF)
|
[here](https://app.swaggerhub.com/apis-docs/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
|
||||||
|
![stirling-login](images/login-light.png)
|
||||||
|
### Prerequisites:
|
||||||
|
- User must have the folder ./configs volumed within docker so that it is retained during updates.
|
||||||
|
- Docker uses must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables.
|
||||||
|
- Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true``
|
||||||
|
- Now the initial user will be generated with username ``admin`` and password ``stirling``. On login you will be forced to change the password to a new one. You can also use the environment variables ``SECURITY_INITIALLOGIN_USERNAME`` and ``SECURITY_INITIALLOGIN_PASSWORD`` to set your own straight away (Recommended to remove them after user creation).
|
||||||
|
|
||||||
|
Once the above has been done, on restart, a new stirling-pdf-DB.mv.db will show if everything worked.
|
||||||
|
|
||||||
|
When you login to Stirling PDF you will be redirected to /login page to login with those default credentials. After login everything should function as normal
|
||||||
|
|
||||||
|
To access your account settings go to Account settings in the settings cog menu (top right in navbar) This Account settings menu is also where you find your API key.
|
||||||
|
|
||||||
|
To add new users go to the bottom of Account settings and hit 'Admin Settings', here you can add new users. The different roles mentioned within this are for rate limiting. This is a Work in progress which will be expanding on more in future
|
||||||
|
|
||||||
|
For API usage you must provide a header with 'X-API-Key' and the associated API key for that user.
|
||||||
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### Q1: Can you add authentication in Stirling PDF?
|
### Q1: What are your planned features?
|
||||||
There is no Auth within Stirling PDF and there is none planned. This feature will not be added. Instead we recommended you use trusted and secure authentication software like Authentik or Authelia.
|
|
||||||
|
|
||||||
### Q2: What are your planned features?
|
|
||||||
- Crop
|
|
||||||
- Progress bar/Tracking
|
- Progress bar/Tracking
|
||||||
- Full custom logic pipelines to combine multiple operations together.
|
- Full custom logic pipelines to combine multiple operations together.
|
||||||
- Folder support with auto scanning to perform operations on
|
- Folder support with auto scanning to perform operations on
|
||||||
- Redact sections of pages
|
- Redact text (Via UI not just automated way)
|
||||||
- Add page numbers
|
- Add Forms
|
||||||
- Auto rename (Renames file based on file title text)
|
- Annotations
|
||||||
- URL to PDF
|
- Multi page layout (Stich PDF pages together) support x rows y columns and custom page sizing
|
||||||
- Change contrast
|
- Fill forms mannual and automatic
|
||||||
|
|
||||||
### Q3: Why is my application downloading .htm files?
|
### Q2: Why is my application downloading .htm files?
|
||||||
This is a issue caused commonly by your NGINX congifuration. 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 a issue caused commonly by your NGINX congifuration. 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.
|
||||||
|
|
||||||
|
### Q3: Why is my download timing out
|
||||||
|
NGINX has timeout values by default so if you are running Stirling-PDF behind NGINX you may need to set a timeout value such as adding the config ``proxy_read_timeout 3600;``
|
||||||
|
|
39
build.gradle
39
build.gradle
|
@ -8,15 +8,33 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '0.12.3'
|
version = '0.14.5'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
if (System.getenv('DOCKER_ENABLE_SECURITY') == 'false') {
|
||||||
|
exclude 'stirling/software/SPDF/config/security/**'
|
||||||
|
exclude 'stirling/software/SPDF/controller/api/UserController.java'
|
||||||
|
exclude 'stirling/software/SPDF/controller/web/AccountWebController.java'
|
||||||
|
exclude 'stirling/software/SPDF/model/ApiKeyAuthenticationToken.java'
|
||||||
|
exclude 'stirling/software/SPDF/model/Authority.java'
|
||||||
|
exclude 'stirling/software/SPDF/model/PersistentLogin.java'
|
||||||
|
exclude 'stirling/software/SPDF/model/User.java'
|
||||||
|
exclude 'stirling/software/SPDF/repository/**'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
openApi {
|
openApi {
|
||||||
apiDocsUrl = "http://localhost:8080/v3/api-docs"
|
apiDocsUrl = "http://localhost:8080/v1/api-docs"
|
||||||
outputDir = file("$projectDir")
|
outputDir = file("$projectDir")
|
||||||
outputFileName = "SwaggerDoc.json"
|
outputFileName = "SwaggerDoc.json"
|
||||||
}
|
}
|
||||||
|
@ -48,7 +66,18 @@ dependencies {
|
||||||
implementation 'org.yaml:snakeyaml:2.1'
|
implementation 'org.yaml:snakeyaml:2.1'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2'
|
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.2'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.2'
|
||||||
|
|
||||||
|
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-security:3.1.2'
|
||||||
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
|
||||||
|
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||||
|
implementation "com.h2database:h2"
|
||||||
|
}
|
||||||
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.2'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.2'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
|
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
|
||||||
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
|
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
|
||||||
implementation 'commons-io:commons-io:2.13.0'
|
implementation 'commons-io:commons-io:2.13.0'
|
||||||
|
@ -57,16 +86,20 @@ dependencies {
|
||||||
|
|
||||||
//general PDF
|
//general PDF
|
||||||
implementation 'org.apache.pdfbox:pdfbox:2.0.29'
|
implementation 'org.apache.pdfbox:pdfbox:2.0.29'
|
||||||
|
implementation 'org.apache.pdfbox:xmpbox:2.0.29'
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||||
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
|
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
|
||||||
implementation 'com.itextpdf:itext7-core:7.2.5'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
implementation 'io.micrometer:micrometer-core'
|
implementation 'io.micrometer:micrometer-core'
|
||||||
implementation group: 'com.google.zxing', name: 'core', version: '3.5.1'
|
implementation group: 'com.google.zxing', name: 'core', version: '3.5.1'
|
||||||
// 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'
|
||||||
|
// https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core
|
||||||
|
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0'
|
||||||
|
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
|
compileOnly 'org.projectlombok:lombok:1.18.28'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok:1.18.28'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
chart/stirling-pdf/Chart.yaml
Normal file
15
chart/stirling-pdf/Chart.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
apiVersion: v2
|
||||||
|
appVersion: 0.14.2
|
||||||
|
description: locally hosted web application that allows you to perform various operations on PDF files
|
||||||
|
home: https://github.com/Frooodle/Stirling-PDF
|
||||||
|
keywords:
|
||||||
|
- stirling-pdf
|
||||||
|
- helm
|
||||||
|
- charts repo
|
||||||
|
maintainers:
|
||||||
|
- name: Frooodle
|
||||||
|
url: https://github.com/Frooodle/Stirling-PDF
|
||||||
|
name: stirling-pdf
|
||||||
|
sources:
|
||||||
|
- https://github.com/Frooodle/Stirling-PDF
|
||||||
|
version: 1.0.0
|
30
chart/stirling-pdf/templates/NOTES.txt
Normal file
30
chart/stirling-pdf/templates/NOTES.txt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
** Please be patient while the chart is being deployed **
|
||||||
|
|
||||||
|
Get the stirlingpdf URL by running:
|
||||||
|
|
||||||
|
{{- if contains "NodePort" .Values.service.type }}
|
||||||
|
|
||||||
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "stirlingpdf.fullname" . }})
|
||||||
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
|
echo http://$NODE_IP:$NODE_PORT/
|
||||||
|
|
||||||
|
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||||
|
|
||||||
|
** Please ensure an external IP is associated to the {{ template "stirlingpdf.fullname" . }} service before proceeding **
|
||||||
|
** Watch the status using: kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "stirlingpdf.fullname" . }} **
|
||||||
|
|
||||||
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "stirlingpdf.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||||
|
echo http://$SERVICE_IP:{{ .Values.service.externalPort }}/
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
export SERVICE_HOST=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "stirlingpdf.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
|
||||||
|
echo http://$SERVICE_HOST:{{ .Values.service.externalPort }}/
|
||||||
|
|
||||||
|
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||||
|
|
||||||
|
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "stirlingpdf.name" . }}" -l "release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||||
|
echo http://127.0.0.1:8080/
|
||||||
|
kubectl port-forward $POD_NAME 8080:8080 --namespace {{ .Release.Namespace }}
|
||||||
|
|
||||||
|
{{- end }}
|
129
chart/stirling-pdf/templates/_helpers.tpl
Normal file
129
chart/stirling-pdf/templates/_helpers.tpl
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- /*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
|
||||||
|
It does minimal escaping for use in Kubernetes labels.
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
|
||||||
|
stirlingpdf-0.4.5
|
||||||
|
*/ -}}
|
||||||
|
{{- define "stirlingpdf.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "stirlingpdf.chart" . }}
|
||||||
|
{{ include "stirlingpdf.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.commonLabels}}
|
||||||
|
{{ toYaml .Values.commonLabels }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "stirlingpdf.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "stirlingpdf.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Return the proper image name to change the volume permissions
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.volumePermissions.image" -}}
|
||||||
|
{{- $registryName := .Values.volumePermissions.image.registry -}}
|
||||||
|
{{- $repositoryName := .Values.volumePermissions.image.repository -}}
|
||||||
|
{{- $tag := .Values.volumePermissions.image.tag | toString -}}
|
||||||
|
{{/*
|
||||||
|
Helm 2.11 supports the assignment of a value to a variable defined in a different scope,
|
||||||
|
but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic.
|
||||||
|
Also, we can't use a single if because lazy evaluation is not an option
|
||||||
|
*/}}
|
||||||
|
{{- if .Values.global }}
|
||||||
|
{{- if .Values.global.imageRegistry }}
|
||||||
|
{{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Return the proper Docker Image Registry Secret Names
|
||||||
|
*/}}
|
||||||
|
{{- define "stirlingpdf.imagePullSecrets" -}}
|
||||||
|
{{/*
|
||||||
|
Helm 2.11 supports the assignment of a value to a variable defined in a different scope,
|
||||||
|
but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic.
|
||||||
|
Also, we can not use a single if because lazy evaluation is not an option
|
||||||
|
*/}}
|
||||||
|
{{- if .Values.global }}
|
||||||
|
{{- if .Values.global.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- range .Values.global.imagePullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if or .Values.image.pullSecrets .Values.volumePermissions.image.pullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- range .Values.image.pullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- range .Values.volumePermissions.image.pullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- else if or .Values.image.pullSecrets .Values.volumePermissions.image.pullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- range .Values.image.pullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- range .Values.volumePermissions.image.pullSecrets }}
|
||||||
|
- name: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
129
chart/stirling-pdf/templates/deployment.yaml
Normal file
129
chart/stirling-pdf/templates/deployment.yaml
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
{{- with .Values.deployment.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- if .Values.deployment.labels }}
|
||||||
|
{{- toYaml .Values.deployment.labels | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "stirlingpdf.selectorLabels" . | nindent 6 }}
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
strategy:
|
||||||
|
{{ toYaml .Values.strategy | indent 4 }}
|
||||||
|
revisionHistoryLimit: 10
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.selectorLabels" . | nindent 8 }}
|
||||||
|
{{- if .Values.podLabels }}
|
||||||
|
{{- toYaml .Values.podLabels | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if .Values.priorityClassName }}
|
||||||
|
priorityClassName: "{{ .Values.priorityClassName }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.securityContext.enabled }}
|
||||||
|
securityContext:
|
||||||
|
fsGroup: {{ .Values.securityContext.fsGroup }}
|
||||||
|
{{- if .Values.securityContext.runAsNonRoot }}
|
||||||
|
runAsNonRoot: {{ .Values.securityContext.runAsNonRoot }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.securityContext.supplementalGroups }}
|
||||||
|
supplementalGroups: {{ .Values.securityContext.supplementalGroups }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if .Values.persistence.enabled }}
|
||||||
|
initContainers:
|
||||||
|
- name: volume-permissions
|
||||||
|
image: {{ template "stirlingpdf.volumePermissions.image" . }}
|
||||||
|
imagePullPolicy: "{{ .Values.volumePermissions.image.pullPolicy }}"
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.containerSecurityContext | nindent 10 }}
|
||||||
|
command: ['sh', '-c', 'chown -R {{ .Values.securityContext.fsGroup }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.path }}']
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: {{ .Values.persistence.path }}
|
||||||
|
name: storage-volume
|
||||||
|
{{- end }}
|
||||||
|
{{- include "stirlingpdf.imagePullSecrets" . | indent 6 }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: {{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.containerSecurityContext | nindent 10 }}
|
||||||
|
{{- if .Values.envs }}
|
||||||
|
env:
|
||||||
|
{{ toYaml .Values.envs | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.extraArgs }}
|
||||||
|
args:
|
||||||
|
{{ toYaml .Values.extraArgs | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
{{ toYaml .Values.probes.livenessHttpGetConfig | indent 12 }}
|
||||||
|
{{ toYaml .Values.probes.liveness | indent 10 }}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
{{ toYaml .Values.probes.readinessHttpGetConfig | indent 12 }}
|
||||||
|
{{ toYaml .Values.probes.readiness | indent 10 }}
|
||||||
|
volumeMounts:
|
||||||
|
{{- if .Values.deployment.extraVolumeMounts }}
|
||||||
|
{{- toYaml .Values.deployment.extraVolumeMounts | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.deployment.sidecarContainers }}
|
||||||
|
{{- range $name, $spec := .Values.deployment.sidecarContainers }}
|
||||||
|
- name: {{ $name }}
|
||||||
|
{{- toYaml $spec | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.resources }}
|
||||||
|
resources:
|
||||||
|
{{ toYaml . | indent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.schedulerName }}
|
||||||
|
schedulerName: {{ .Values.schedulerName }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ include "stirlingpdf.serviceAccountName" . }}
|
||||||
|
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||||
|
volumes:
|
||||||
|
{{- if .Values.deployment.extraVolumes }}
|
||||||
|
{{- toYaml .Values.deployment.extraVolumes | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
|
- name: storage-volume
|
||||||
|
{{- if .Values.persistence.enabled }}
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ .Values.persistence.existingClaim | default (include "stirlingpdf.fullname" .) }}
|
||||||
|
{{- else }}
|
||||||
|
emptyDir: {}
|
||||||
|
{{- end }}
|
85
chart/stirling-pdf/templates/ingress.yaml
Normal file
85
chart/stirling-pdf/templates/ingress.yaml
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- $servicePort := .Values.service.externalPort -}}
|
||||||
|
{{- $serviceName := include "stirlingpdf.fullname" . -}}
|
||||||
|
{{- $ingressExtraPaths := .Values.ingress.extraPaths -}}
|
||||||
|
---
|
||||||
|
{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion }}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
{{- else if semverCompare "<1.19-0" .Capabilities.KubeVersion.GitVersion }}
|
||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
{{- else }}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
{{- end }}
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.ingress.ingressClassName }}
|
||||||
|
ingressClassName: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ .name }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
{{- range $ingressExtraPaths }}
|
||||||
|
- path: {{ default "/" .path | quote }}
|
||||||
|
backend:
|
||||||
|
{{- if semverCompare "<1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||||
|
{{- if $.Values.service.servicename }}
|
||||||
|
serviceName: {{ $.Values.service.servicename }}
|
||||||
|
{{- else }}
|
||||||
|
serviceName: {{ default $serviceName .service }}
|
||||||
|
{{- end }}
|
||||||
|
servicePort: {{ default $servicePort .port }}
|
||||||
|
{{- else }}
|
||||||
|
service:
|
||||||
|
{{- if $.Values.service.servicename }}
|
||||||
|
name: {{ $.Values.service.servicename }}
|
||||||
|
{{- else }}
|
||||||
|
name: {{ default $serviceName .service }}
|
||||||
|
{{- end }}
|
||||||
|
port:
|
||||||
|
number: {{ default $servicePort .port }}
|
||||||
|
pathType: {{ default $.Values.ingress.pathType .pathType }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
- path: {{ default "/" .path | quote }}
|
||||||
|
backend:
|
||||||
|
{{- if semverCompare "<1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||||
|
{{- if $.Values.service.servicename }}
|
||||||
|
serviceName: {{ $.Values.service.servicename }}
|
||||||
|
{{- else }}
|
||||||
|
serviceName: {{ default $serviceName .service }}
|
||||||
|
{{- end }}
|
||||||
|
servicePort: {{ default $servicePort .servicePort }}
|
||||||
|
{{- else }}
|
||||||
|
service:
|
||||||
|
{{- if $.Values.service.servicename }}
|
||||||
|
name: {{ $.Values.service.servicename }}
|
||||||
|
{{- else }}
|
||||||
|
name: {{ default $serviceName .service }}
|
||||||
|
{{- end }}
|
||||||
|
port:
|
||||||
|
number: {{ default $servicePort .port }}
|
||||||
|
pathType: {{ $.Values.ingress.pathType }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
tls:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
{{- if .tls }}
|
||||||
|
- hosts:
|
||||||
|
- {{ .name }}
|
||||||
|
secretName: {{ .tlsSecret }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
16
chart/stirling-pdf/templates/pv.yaml
Normal file
16
chart/stirling-pdf/templates/pv.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{{- if .Values.persistence.pv.enabled -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.persistence.pv.pvname | default (include "stirlingpdf.fullname" .) }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: {{ .Values.persistence.pv.capacity.storage }}
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.pv.accessMode | quote }}
|
||||||
|
nfs:
|
||||||
|
server: {{ .Values.persistence.pv.nfs.server }}
|
||||||
|
path: {{ .Values.persistence.pv.nfs.path | quote }}
|
||||||
|
{{- end }}
|
27
chart/stirling-pdf/templates/pvc.yaml
Normal file
27
chart/stirling-pdf/templates/pvc.yaml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}}
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.persistence.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- {{ .Values.persistence.accessMode | quote }}
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.size | quote }}
|
||||||
|
{{- if .Values.persistence.storageClass }}
|
||||||
|
{{- if (eq "-" .Values.persistence.storageClass) }}
|
||||||
|
storageClassName: ""
|
||||||
|
{{- else }}
|
||||||
|
storageClassName: "{{ .Values.persistence.storageClass }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.persistence.volumeName }}
|
||||||
|
volumeName: "{{ .Values.persistence.volumeName }}"
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
48
chart/stirling-pdf/templates/service.yaml
Normal file
48
chart/stirling-pdf/templates/service.yaml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.service.servicename | default (include "stirlingpdf.fullname" .) }}
|
||||||
|
{{- with .Values.service.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.service.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
{{- if (or (eq .Values.service.type "LoadBalancer") (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort)))) }}
|
||||||
|
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if (and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerIP) }}
|
||||||
|
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if (and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerSourceRanges) }}
|
||||||
|
loadBalancerSourceRanges:
|
||||||
|
{{- with .Values.service.loadBalancerSourceRanges }}
|
||||||
|
{{ toYaml . | indent 2 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq .Values.service.type "ClusterIP" }}
|
||||||
|
{{- if .Values.service.clusterIP }}
|
||||||
|
clusterIP: {{ .Values.service.clusterIP }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.externalPort }}
|
||||||
|
{{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }}
|
||||||
|
nodePort: {{.Values.service.nodePort}}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.service.targetPort }}
|
||||||
|
targetPort: {{ .Values.service.targetPort }}
|
||||||
|
name: {{ .Values.service.targetPort }}
|
||||||
|
{{- else }}
|
||||||
|
targetPort: http
|
||||||
|
name: http
|
||||||
|
{{- end }}
|
||||||
|
protocol: TCP
|
||||||
|
|
||||||
|
selector:
|
||||||
|
{{- include "stirlingpdf.selectorLabels" . | nindent 4 }}
|
13
chart/stirling-pdf/templates/serviceaccount.yaml
Normal file
13
chart/stirling-pdf/templates/serviceaccount.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{{- if .Values.serviceAccount.create -}}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.serviceAccountName" . }}
|
||||||
|
{{- with .Values.serviceAccount.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{ toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- end }}
|
31
chart/stirling-pdf/templates/servicemonitor.yaml
Normal file
31
chart/stirling-pdf/templates/servicemonitor.yaml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{{- if and ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) ( .Values.serviceMonitor.enabled ) }}
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "stirlingpdf.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.serviceMonitor.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
endpoints:
|
||||||
|
- targetPort: 8080
|
||||||
|
{{- if .Values.serviceMonitor.interval }}
|
||||||
|
interval: {{ .Values.serviceMonitor.interval }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.serviceMonitor.metricsPath }}
|
||||||
|
path: {{ .Values.serviceMonitor.metricsPath }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.serviceMonitor.timeout }}
|
||||||
|
scrapeTimeout: {{ .Values.serviceMonitor.timeout }}
|
||||||
|
{{- end }}
|
||||||
|
jobLabel: {{ include "stirlingpdf.fullname" . }}
|
||||||
|
namespaceSelector:
|
||||||
|
matchNames:
|
||||||
|
- {{ .Release.Namespace }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "stirlingpdf.selectorLabels" . | nindent 6 }}
|
||||||
|
{{- end }}
|
239
chart/stirling-pdf/values.yaml
Normal file
239
chart/stirling-pdf/values.yaml
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
extraArgs: []
|
||||||
|
# - --storage-timestamp-tolerance 1s
|
||||||
|
replicaCount: 1
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
image:
|
||||||
|
repository: frooodle/s-pdf
|
||||||
|
# took Chart appVersion by default
|
||||||
|
tag: ~
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
secret:
|
||||||
|
labels: {}
|
||||||
|
## Labels to apply to all resources
|
||||||
|
##
|
||||||
|
commonLabels: {}
|
||||||
|
# team_name: dev
|
||||||
|
|
||||||
|
envs: []
|
||||||
|
# - name: PP_HOME_NAME
|
||||||
|
# value: "Stirling PDF"
|
||||||
|
# - name: APP_HOME_DESCRIPTION
|
||||||
|
# value: "Your locally hosted one-stop-shop for all your PDF needs."
|
||||||
|
# - name: APP_NAVBAR_NAME
|
||||||
|
# value: "Stirling PDF"
|
||||||
|
# - name: ALLOW_GOOGLE_VISIBILITY
|
||||||
|
# value: "true"
|
||||||
|
# - name: APP_ROOT_PATH
|
||||||
|
# value: "/"
|
||||||
|
# - name: APP_LOCALE
|
||||||
|
# value: "en_GB"
|
||||||
|
|
||||||
|
deployment:
|
||||||
|
## stirling-pdf Deployment annotations
|
||||||
|
annotations: {}
|
||||||
|
# name: value
|
||||||
|
labels: {}
|
||||||
|
# name: value
|
||||||
|
# additional volumes
|
||||||
|
extraVolumes: []
|
||||||
|
# - name: nginx-config
|
||||||
|
# secret:
|
||||||
|
# secretName: nginx-config
|
||||||
|
# additional volumes to mount
|
||||||
|
extraVolumeMounts: []
|
||||||
|
## sidecarContainers for the stirling-pdf
|
||||||
|
# Can be used to add a proxy to the pod that does
|
||||||
|
# scanning for secrets, signing, authentication, validation
|
||||||
|
# of the chart's content, send notifications...
|
||||||
|
sidecarContainers: {}
|
||||||
|
## Example sidecarContainer which uses an extraVolume from above and
|
||||||
|
## a named port that can be referenced in the service as targetPort.
|
||||||
|
# proxy:
|
||||||
|
# image: nginx:latest
|
||||||
|
# ports:
|
||||||
|
# - name: proxy
|
||||||
|
# containerPort: 8081
|
||||||
|
# volumeMounts:
|
||||||
|
# - name: nginx-config
|
||||||
|
# readOnly: true
|
||||||
|
# mountPath: /etc/nginx
|
||||||
|
|
||||||
|
## Pod annotations
|
||||||
|
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
|
||||||
|
## Read more about kube2iam to provide access to s3 https://github.com/jtblin/kube2iam
|
||||||
|
##
|
||||||
|
podAnnotations: {}
|
||||||
|
# iam.amazonaws.com/role: role-arn
|
||||||
|
|
||||||
|
## Pod labels
|
||||||
|
## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||||
|
podLabels: {}
|
||||||
|
# name: value
|
||||||
|
|
||||||
|
service:
|
||||||
|
servicename:
|
||||||
|
type: ClusterIP
|
||||||
|
externalTrafficPolicy: Local
|
||||||
|
## Uses pre-assigned IP address from cloud provider
|
||||||
|
## Only valid if service.type: LoadBalancer
|
||||||
|
loadBalancerIP:
|
||||||
|
## Limits which cidr blocks can connect to service's load balancer
|
||||||
|
## Only valid if service.type: LoadBalancer
|
||||||
|
loadBalancerSourceRanges: []
|
||||||
|
# clusterIP: None
|
||||||
|
externalPort: 8080
|
||||||
|
## targetPort of the container to use. If a sidecar should handle the
|
||||||
|
## requests first, use the named port from the sidecar. See sidecar example
|
||||||
|
## from deployment above. Leave empty to use stirling-pdf directly.
|
||||||
|
targetPort:
|
||||||
|
nodePort:
|
||||||
|
annotations: {}
|
||||||
|
labels: {}
|
||||||
|
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
|
# namespace: prometheus
|
||||||
|
labels: {}
|
||||||
|
metricsPath: "/metrics"
|
||||||
|
# timeout: 60
|
||||||
|
# interval: 60
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
# requests:
|
||||||
|
# cpu: 80m
|
||||||
|
# memory: 64Mi
|
||||||
|
|
||||||
|
probes:
|
||||||
|
liveness:
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 1
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessHttpGetConfig:
|
||||||
|
scheme: HTTP
|
||||||
|
readiness:
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 1
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessHttpGetConfig:
|
||||||
|
scheme: HTTP
|
||||||
|
|
||||||
|
serviceAccount:
|
||||||
|
create: true
|
||||||
|
name: ""
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
## Annotations for the Service Account
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
# UID/GID 1000 is the default user "stirling-pdf" used in
|
||||||
|
# the container image starting in v0.8.0 and above. This
|
||||||
|
# is required for local persistent storage. If your cluster
|
||||||
|
# does not allow this, try setting securityContext: {}
|
||||||
|
securityContext:
|
||||||
|
enabled: true
|
||||||
|
fsGroup: 1000
|
||||||
|
## Optionally, specify supplementalGroups and/or
|
||||||
|
## runAsNonRoot for security purposes
|
||||||
|
# runAsNonRoot: true
|
||||||
|
# supplementalGroups: [1000]
|
||||||
|
|
||||||
|
containerSecurityContext: {}
|
||||||
|
|
||||||
|
priorityClassName: ""
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
enabled: false
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
size: 8Gi
|
||||||
|
labels: {}
|
||||||
|
# name: value
|
||||||
|
path: /tmp
|
||||||
|
## A manually managed Persistent Volume and Claim
|
||||||
|
## Requires persistence.enabled: true
|
||||||
|
## If defined, PVC must be created manually before volume will be bound
|
||||||
|
# existingClaim:
|
||||||
|
|
||||||
|
## stirling-pdf data Persistent Volume Storage Class
|
||||||
|
## If defined, storageClassName: <storageClass>
|
||||||
|
## If set to "-", storageClassName: "", which disables dynamic provisioning
|
||||||
|
## If undefined (the default) or set to null, no storageClassName spec is
|
||||||
|
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
||||||
|
## GKE, AWS & OpenStack)
|
||||||
|
##
|
||||||
|
# storageClass: "-"
|
||||||
|
# volumeName:
|
||||||
|
pv:
|
||||||
|
enabled: false
|
||||||
|
pvname:
|
||||||
|
capacity:
|
||||||
|
storage: 8Gi
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
nfs:
|
||||||
|
server:
|
||||||
|
path:
|
||||||
|
|
||||||
|
## Init containers parameters:
|
||||||
|
## volumePermissions: Change the owner of the persistent volume mountpoint to RunAsUser:fsGroup
|
||||||
|
##
|
||||||
|
volumePermissions:
|
||||||
|
image:
|
||||||
|
registry: docker.io
|
||||||
|
repository: bitnami/minideb
|
||||||
|
tag: buster
|
||||||
|
pullPolicy: Always
|
||||||
|
## Optionally specify an array of imagePullSecrets.
|
||||||
|
## Secrets must be manually created in the namespace.
|
||||||
|
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
|
||||||
|
##
|
||||||
|
# pullSecrets:
|
||||||
|
# - myRegistryKeySecretName
|
||||||
|
|
||||||
|
## Ingress for load balancer
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
pathType: "ImplementationSpecific"
|
||||||
|
## stirling-pdf Ingress labels
|
||||||
|
##
|
||||||
|
labels: {}
|
||||||
|
# dns: "route53"
|
||||||
|
|
||||||
|
## stirling-pdf Ingress annotations
|
||||||
|
##
|
||||||
|
annotations: {}
|
||||||
|
# kubernetes.io/ingress.class: nginx
|
||||||
|
# kubernetes.io/tls-acme: "true"
|
||||||
|
|
||||||
|
## stirling-pdf Ingress hostnames
|
||||||
|
## Must be provided if Ingress is enabled
|
||||||
|
##
|
||||||
|
hosts: []
|
||||||
|
# - name: stirling-pdf.domain1.com
|
||||||
|
# path: /
|
||||||
|
# tls: false
|
||||||
|
# - name: stirling-pdf.domain2.com
|
||||||
|
# path: /
|
||||||
|
#
|
||||||
|
# ## Set this to true in order to enable TLS on the ingress record
|
||||||
|
# tls: true
|
||||||
|
#
|
||||||
|
# ## If TLS is set to true, you must declare what secret will store the key/certificate for TLS
|
||||||
|
# ## Secrets must be added manually to the namespace
|
||||||
|
# tlsSecret: stirling-pdf.domain2-tls
|
||||||
|
|
||||||
|
# For Kubernetes >= 1.18 you should specify the ingress-controller via the field ingressClassName
|
||||||
|
# See https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/#specifying-the-class-of-an-ingress
|
||||||
|
ingressClassName:
|
||||||
|
|
BIN
images/login-dark.png
Normal file
BIN
images/login-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
images/login-light.png
Normal file
BIN
images/login-light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 131 KiB |
|
@ -1,5 +1,4 @@
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,25 @@ if [[ -n "$TESSERACT_LANGS" ]]; then
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check for DOCKER_ENABLE_SECURITY and download the appropriate JAR if required
|
||||||
|
if [ "$DOCKER_ENABLE_SECURITY" = "true" ] && [ "$VERSION_TAG" != "alpha" ]; then
|
||||||
|
if [ ! -f app-security.jar ]; then
|
||||||
|
echo "Trying to download from: https://github.com/Frooodle/Stirling-PDF/releases/download/v$VERSION_TAG/Stirling-PDF-with-login.jar"
|
||||||
|
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 [ $? -ne 0 ]; then
|
||||||
|
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/Frooodle/Stirling-PDF/releases/download/$VERSION_TAG/Stirling-PDF-with-login.jar
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then # checks if curl was successful
|
||||||
|
rm -f app.jar
|
||||||
|
ln -s app-security.jar app.jar
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
# Run the main command
|
# Run the main command
|
||||||
exec "$@"
|
exec "$@"
|
|
@ -1,20 +1,19 @@
|
||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import stirling.software.SPDF.config.ConfigInitializer;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
|
||||||
//@EnableScheduling
|
//@EnableScheduling
|
||||||
public class SPdfApplication {
|
public class SPdfApplication {
|
||||||
|
|
||||||
|
@ -48,7 +47,15 @@ public class SPdfApplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(SPdfApplication.class, args);
|
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
||||||
|
app.addInitializers(new ConfigInitializer());
|
||||||
|
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
||||||
|
app.setDefaultProperties(Collections.singletonMap("spring.config.additional-location", "file:configs/settings.yml"));
|
||||||
|
} else {
|
||||||
|
System.out.println("External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
|
||||||
|
}
|
||||||
|
app.run(args);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
@ -58,7 +65,6 @@ public class SPdfApplication {
|
||||||
|
|
||||||
GeneralUtils.createDir("customFiles/static/");
|
GeneralUtils.createDir("customFiles/static/");
|
||||||
GeneralUtils.createDir("customFiles/templates/");
|
GeneralUtils.createDir("customFiles/templates/");
|
||||||
GeneralUtils.createDir("config");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.PropertyEditorRegistrar;
|
||||||
|
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 stirling.software.SPDF.model.ApplicationProperties;
|
||||||
@Configuration
|
@Configuration
|
||||||
public class AppConfig {
|
public class AppConfig {
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Bean(name = "loginEnabled")
|
||||||
|
public boolean loginEnabled() {
|
||||||
|
return applicationProperties.getSecurity().getEnableLogin();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(name = "appName")
|
@Bean(name = "appName")
|
||||||
public String appName() {
|
public String appName() {
|
||||||
String appName = System.getProperty("APP_HOME_NAME");
|
String homeTitle = applicationProperties.getUi().getAppName();
|
||||||
if (appName == null)
|
return (homeTitle != null) ? homeTitle : "Stirling PDF";
|
||||||
appName = System.getenv("APP_HOME_NAME");
|
|
||||||
return (appName != null) ? appName : "Stirling PDF";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "appVersion")
|
@Bean(name = "appVersion")
|
||||||
|
@ -21,22 +32,24 @@ public class AppConfig {
|
||||||
|
|
||||||
@Bean(name = "homeText")
|
@Bean(name = "homeText")
|
||||||
public String homeText() {
|
public String homeText() {
|
||||||
String homeText = System.getProperty("APP_HOME_DESCRIPTION");
|
return (applicationProperties.getUi().getHomeDescription() != null) ? applicationProperties.getUi().getHomeDescription() : "null";
|
||||||
if (homeText == null)
|
|
||||||
homeText = System.getenv("APP_HOME_DESCRIPTION");
|
|
||||||
return (homeText != null) ? homeText : "null";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Bean(name = "navBarText")
|
@Bean(name = "navBarText")
|
||||||
public String navBarText() {
|
public String navBarText() {
|
||||||
String navBarText = System.getProperty("APP_NAVBAR_NAME");
|
String defaultNavBar = applicationProperties.getUi().getAppNameNavbar() != null ? applicationProperties.getUi().getAppNameNavbar() : applicationProperties.getUi().getAppName();
|
||||||
if (navBarText == null)
|
return (defaultNavBar != null) ? defaultNavBar : "Stirling PDF";
|
||||||
navBarText = System.getenv("APP_NAVBAR_NAME");
|
|
||||||
if (navBarText == null)
|
|
||||||
navBarText = System.getProperty("APP_HOME_NAME");
|
|
||||||
if (navBarText == null)
|
|
||||||
navBarText = System.getenv("APP_HOME_NAME");
|
|
||||||
|
|
||||||
return (navBarText != null) ? navBarText : "Stirling PDF";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean(name = "rateLimit")
|
||||||
|
public boolean rateLimit() {
|
||||||
|
String appName = System.getProperty("rateLimit");
|
||||||
|
if (appName == null)
|
||||||
|
appName = System.getenv("rateLimit");
|
||||||
|
System.out.println("rateLimit=" + appName);
|
||||||
|
return (appName != null) ? Boolean.valueOf(appName) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.LocaleResolver;
|
import org.springframework.web.servlet.LocaleResolver;
|
||||||
|
@ -10,9 +11,14 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class Beans implements WebMvcConfigurer {
|
public class Beans implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(localeChangeInterceptor());
|
registry.addInterceptor(localeChangeInterceptor());
|
||||||
|
@ -30,9 +36,8 @@ public class Beans implements WebMvcConfigurer {
|
||||||
public LocaleResolver localeResolver() {
|
public LocaleResolver localeResolver() {
|
||||||
SessionLocaleResolver slr = new SessionLocaleResolver();
|
SessionLocaleResolver slr = new SessionLocaleResolver();
|
||||||
|
|
||||||
String appLocaleEnv = System.getProperty("APP_LOCALE");
|
|
||||||
if (appLocaleEnv == null)
|
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();
|
||||||
appLocaleEnv = System.getenv("APP_LOCALE");
|
|
||||||
Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set
|
Locale defaultLocale = Locale.UK; // Fallback to UK locale if environment variable is not set
|
||||||
|
|
||||||
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
|
||||||
|
|
|
@ -1,30 +1,20 @@
|
||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
public class CleanUrlInterceptor implements HandlerInterceptor {
|
public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints");
|
private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints", "logout", "error", "file", "messageType");
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||||
|
@ -32,7 +22,6 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
||||||
String queryString = request.getQueryString();
|
String queryString = request.getQueryString();
|
||||||
if (queryString != null && !queryString.isEmpty()) {
|
if (queryString != null && !queryString.isEmpty()) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
|
|
||||||
Map<String, String> parameters = new HashMap<>();
|
Map<String, String> parameters = new HashMap<>();
|
||||||
|
|
||||||
// Keep only the allowed parameters
|
// Keep only the allowed parameters
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContextInitializer;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
|
||||||
|
public class ConfigInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||||
|
try {
|
||||||
|
ensureConfigExists();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to initialize application configuration", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ensureConfigExists() throws IOException {
|
||||||
|
// Define the path to the external config directory
|
||||||
|
Path destPath = Paths.get("configs", "settings.yml");
|
||||||
|
|
||||||
|
// Check if the file already exists
|
||||||
|
if (Files.notExists(destPath)) {
|
||||||
|
// Ensure the destination directory exists
|
||||||
|
Files.createDirectories(destPath.getParent());
|
||||||
|
|
||||||
|
// Copy the resource from classpath to the external directory
|
||||||
|
try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
|
||||||
|
if (in != null) {
|
||||||
|
Files.copy(in, destPath);
|
||||||
|
} else {
|
||||||
|
throw new FileNotFoundException("Resource file not found: settings.yml.template");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If user file exists, we need to merge it with the template from the classpath
|
||||||
|
List<String> templateLines;
|
||||||
|
try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
|
||||||
|
templateLines = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).lines()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeYamlFiles(templateLines, destPath, destPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mergeYamlFiles(List<String> templateLines, Path userFilePath, Path outputPath) throws IOException {
|
||||||
|
List<String> userLines = Files.readAllLines(userFilePath);
|
||||||
|
List<String> mergedLines = new ArrayList<>();
|
||||||
|
boolean insideAutoGenerated = false;
|
||||||
|
boolean beforeFirstKey = true;
|
||||||
|
|
||||||
|
Function<String, Boolean> isCommented = line -> line.trim().startsWith("#");
|
||||||
|
Function<String, String> extractKey = line -> {
|
||||||
|
String[] parts = line.split(":");
|
||||||
|
return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
for (String line : templateLines) {
|
||||||
|
String key = extractKey.apply(line);
|
||||||
|
|
||||||
|
if (line.trim().equalsIgnoreCase("AutomaticallyGenerated:")) {
|
||||||
|
insideAutoGenerated = true;
|
||||||
|
mergedLines.add(line);
|
||||||
|
continue;
|
||||||
|
} else if (insideAutoGenerated && line.trim().isEmpty()) {
|
||||||
|
insideAutoGenerated = false;
|
||||||
|
mergedLines.add(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) {
|
||||||
|
// Handle top comments and empty lines before the first key.
|
||||||
|
mergedLines.add(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key.isEmpty())
|
||||||
|
beforeFirstKey = false;
|
||||||
|
|
||||||
|
if (userKeys.contains(key)) {
|
||||||
|
// If user has any version (commented or uncommented) of this key, skip the
|
||||||
|
// template line
|
||||||
|
Optional<String> userValue = userLines.stream()
|
||||||
|
.filter(l -> extractKey.apply(l).equalsIgnoreCase(key) && !isCommented.apply(l)).findFirst();
|
||||||
|
if (userValue.isPresent())
|
||||||
|
mergedLines.add(userValue.get());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCommented.apply(line) || line.trim().isEmpty() || !userKeys.contains(key)) {
|
||||||
|
mergedLines.add(line); // If line is commented, empty or key not present in user's file, retain the
|
||||||
|
// template line
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any additional uncommented user lines that are not present in the
|
||||||
|
// template
|
||||||
|
for (String userLine : userLines) {
|
||||||
|
String userKey = extractKey.apply(userLine);
|
||||||
|
boolean isPresentInTemplate = templateLines.stream().map(extractKey)
|
||||||
|
.anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey));
|
||||||
|
if (!isPresentInTemplate && !isCommented.apply(userLine)) {
|
||||||
|
mergedLines.add(userLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.write(outputPath, mergedLines, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,20 +1,28 @@
|
||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
@Service
|
@Service
|
||||||
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<>();
|
||||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public EndpointConfiguration() {
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public EndpointConfiguration(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
init();
|
init();
|
||||||
processEnvironmentConfigs();
|
processEnvironmentConfigs();
|
||||||
}
|
}
|
||||||
|
@ -200,19 +208,17 @@ public class EndpointConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processEnvironmentConfigs() {
|
private void processEnvironmentConfigs() {
|
||||||
String endpointsToRemove = System.getenv("ENDPOINTS_TO_REMOVE");
|
List<String> endpointsToRemove = applicationProperties.getEndpoints().getToRemove();
|
||||||
String groupsToRemove = System.getenv("GROUPS_TO_REMOVE");
|
List<String> groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();
|
||||||
|
|
||||||
if (endpointsToRemove != null) {
|
if (endpointsToRemove != null) {
|
||||||
String[] endpoints = endpointsToRemove.split(",");
|
for (String endpoint : endpointsToRemove) {
|
||||||
for (String endpoint : endpoints) {
|
|
||||||
disableEndpoint(endpoint.trim());
|
disableEndpoint(endpoint.trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupsToRemove != null) {
|
if (groupsToRemove != null) {
|
||||||
String[] groups = groupsToRemove.split(",");
|
for (String group : groupsToRemove) {
|
||||||
for (String group : groups) {
|
|
||||||
disableGroup(group.trim());
|
disableGroup(group.trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@ -18,14 +14,9 @@ public class OpenApiConfig {
|
||||||
public OpenAPI customOpenAPI() {
|
public OpenAPI customOpenAPI() {
|
||||||
String version = getClass().getPackage().getImplementationVersion();
|
String version = getClass().getPackage().getImplementationVersion();
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
Properties props = new Properties();
|
|
||||||
try (InputStream input = getClass().getClassLoader().getResourceAsStream("version.properties")) {
|
|
||||||
props.load(input);
|
|
||||||
version = props.getProperty("version");
|
|
||||||
} catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
version = "1.0.0"; // default version if all else fails
|
version = "1.0.0"; // default version if all else fails
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OpenAPI().components(new Components()).info(
|
return new OpenAPI().components(new Components()).info(
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.event.ContextRefreshedEvent;
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
|
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
// Handler for external static resources
|
// Handler for external static resources
|
||||||
registry.addResourceHandler("/**")
|
registry.addResourceHandler("/**")
|
||||||
.addResourceLocations("file:customFiles/static/", "classpath:/static/")
|
.addResourceLocations("file:customFiles/static/", "classpath:/static/");
|
||||||
.setCachePeriod(0); // Optional: disable caching
|
//.setCachePeriod(0); // Optional: disable caching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
|
||||||
|
import org.springframework.core.env.PropertiesPropertySource;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
|
import org.springframework.core.io.support.EncodedResource;
|
||||||
|
import org.springframework.core.io.support.PropertySourceFactory;
|
||||||
|
public class YamlPropertySourceFactory implements PropertySourceFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
|
||||||
|
throws IOException {
|
||||||
|
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
|
||||||
|
factory.setResources(encodedResource.getResource());
|
||||||
|
|
||||||
|
Properties properties = factory.getObject();
|
||||||
|
|
||||||
|
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.authentication.LockedException;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)) {
|
||||||
|
setDefaultFailureUrl("/login?error=badcredentials");
|
||||||
|
} else if (exception.getClass().isAssignableFrom(LockedException.class)) {
|
||||||
|
setDefaultFailureUrl("/login?error=locked");
|
||||||
|
}
|
||||||
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.Authority;
|
||||||
|
import stirling.software.SPDF.model.User;
|
||||||
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("No user found with username: " + username));
|
||||||
|
|
||||||
|
return new org.springframework.security.core.userdetails.User(
|
||||||
|
user.getUsername(),
|
||||||
|
user.getPassword(),
|
||||||
|
user.isEnabled(),
|
||||||
|
true, true, true,
|
||||||
|
getAuthorities(user.getAuthorities())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<? extends GrantedAuthority> getAuthorities(Set<Authority> authorities) {
|
||||||
|
return authorities.stream()
|
||||||
|
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class FirstLoginFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
String method = request.getMethod();
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
// Check if the request is for static resources
|
||||||
|
boolean isStaticResource = requestURI.startsWith("/css/")
|
||||||
|
|| requestURI.startsWith("/js/")
|
||||||
|
|| requestURI.startsWith("/images/")
|
||||||
|
|| requestURI.startsWith("/public/")
|
||||||
|
|| requestURI.endsWith(".svg");
|
||||||
|
|
||||||
|
// If it's a static resource, just continue the filter chain and skip the logic below
|
||||||
|
if (isStaticResource) {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
Optional<User> user = userService.findByUsername(authentication.getName());
|
||||||
|
if ("GET".equalsIgnoreCase(method) && user.isPresent() && user.get().isFirstLogin() && !"/change-creds".equals(requestURI)) {
|
||||||
|
response.sendRedirect("/change-creds");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.Role;
|
||||||
|
@Component
|
||||||
|
public class InitialSecuritySetup {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
if (!userService.hasUsers()) {
|
||||||
|
|
||||||
|
|
||||||
|
String initialUsername = applicationProperties.getSecurity().getInitialLogin().getUsername();
|
||||||
|
String initialPassword = applicationProperties.getSecurity().getInitialLogin().getPassword();
|
||||||
|
if (initialUsername != null && initialPassword != null) {
|
||||||
|
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());
|
||||||
|
} else {
|
||||||
|
initialUsername = "admin";
|
||||||
|
initialPassword = "stirling";
|
||||||
|
userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initSecretKey() throws IOException {
|
||||||
|
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||||
|
if (secretKey == null || secretKey.isEmpty()) {
|
||||||
|
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||||
|
saveKeyToConfig(secretKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveKeyToConfig(String key) throws IOException {
|
||||||
|
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||||
|
List<String> lines = Files.readAllLines(path);
|
||||||
|
boolean keyFound = false;
|
||||||
|
|
||||||
|
// Search for the existing key to replace it or place to add it
|
||||||
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
if (lines.get(i).startsWith("AutomaticallyGenerated:")) {
|
||||||
|
keyFound = true;
|
||||||
|
if (i + 1 < lines.size() && lines.get(i + 1).trim().startsWith("key:")) {
|
||||||
|
lines.set(i + 1, " key: " + key);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
lines.add(i + 1, " key: " + key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the section doesn't exist, append it
|
||||||
|
if (!keyFound) {
|
||||||
|
lines.add("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||||
|
lines.add("AutomaticallyGenerated:");
|
||||||
|
lines.add(" key: " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back to the file
|
||||||
|
Files.write(path, lines);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity()
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("loginEnabled")
|
||||||
|
public boolean loginEnabledValue;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserAuthenticationFilter userAuthenticationFilter;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FirstLoginFilter firstLoginFilter;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
if(loginEnabledValue) {
|
||||||
|
|
||||||
|
http.csrf(csrf -> csrf.disable());
|
||||||
|
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
http
|
||||||
|
.formLogin(formLogin -> formLogin
|
||||||
|
.loginPage("/login")
|
||||||
|
.defaultSuccessUrl("/")
|
||||||
|
.failureHandler(new CustomAuthenticationFailureHandler())
|
||||||
|
.permitAll()
|
||||||
|
)
|
||||||
|
.logout(logout -> logout
|
||||||
|
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
||||||
|
.logoutSuccessUrl("/login?logout=true")
|
||||||
|
.invalidateHttpSession(true) // Invalidate session
|
||||||
|
.deleteCookies("JSESSIONID", "remember-me")
|
||||||
|
).rememberMe(rememberMeConfigurer -> rememberMeConfigurer // Use the configurator directly
|
||||||
|
.key("uniqueAndSecret")
|
||||||
|
.tokenRepository(persistentTokenRepository())
|
||||||
|
.tokenValiditySeconds(1209600) // 2 weeks
|
||||||
|
)
|
||||||
|
.authorizeHttpRequests(authz -> authz
|
||||||
|
.requestMatchers(req -> req.getRequestURI().startsWith("/login") || req.getRequestURI().endsWith(".svg") || req.getRequestURI().startsWith("/register") || req.getRequestURI().startsWith("/error") || req.getRequestURI().startsWith("/images/") || req.getRequestURI().startsWith("/public/") || req.getRequestURI().startsWith("/css/") || req.getRequestURI().startsWith("/js/"))
|
||||||
|
.permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.userDetailsService(userDetailsService)
|
||||||
|
.authenticationProvider(authenticationProvider());
|
||||||
|
} else {
|
||||||
|
http.csrf(csrf -> csrf.disable())
|
||||||
|
.authorizeHttpRequests(authz -> authz
|
||||||
|
.anyRequest().permitAll()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DaoAuthenticationProvider authenticationProvider() {
|
||||||
|
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
|
authProvider.setUserDetailsService(userDetailsService);
|
||||||
|
authProvider.setPasswordEncoder(passwordEncoder());
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PersistentTokenRepository persistentTokenRepository() {
|
||||||
|
return new JPATokenRepositoryImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
||||||
|
@Component
|
||||||
|
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("loginEnabled")
|
||||||
|
public boolean loginEnabledValue;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
if (!loginEnabledValue) {
|
||||||
|
// If login is not enabled, just pass all requests without authentication
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
|
// Check for API key in the request headers if no authentication exists
|
||||||
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
|
String apiKey = request.getHeader("X-API-Key");
|
||||||
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||||
|
try {
|
||||||
|
// Use API key to authenticate. This requires you to have an authentication provider for API keys.
|
||||||
|
UserDetails userDetails = userService.loadUserByApiKey(apiKey);
|
||||||
|
if(userDetails == null)
|
||||||
|
{
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
response.getWriter().write("Invalid API Key.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
authentication = new ApiKeyAuthenticationToken(userDetails, apiKey, userDetails.getAuthorities());
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
} catch (AuthenticationException e) {
|
||||||
|
// If API key authentication fails, deny the request
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
response.getWriter().write("Invalid API Key.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we still don't have any authentication, deny the request
|
||||||
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
|
String method = request.getMethod();
|
||||||
|
if ("GET".equalsIgnoreCase(method) && !"/login".equals(requestURI)) {
|
||||||
|
response.sendRedirect("/login"); // redirect to the login page
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
response.getWriter().write("Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternativly you can disable authentication if this is unexpected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
|
||||||
|
String uri = request.getRequestURI();
|
||||||
|
|
||||||
|
String[] permitAllPatterns = {
|
||||||
|
"/login",
|
||||||
|
"/register",
|
||||||
|
"/error",
|
||||||
|
"/images/",
|
||||||
|
"/public/",
|
||||||
|
"/css/",
|
||||||
|
"/js/"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String pattern : permitAllPatterns) {
|
||||||
|
if (uri.startsWith(pattern) || uri.endsWith(".svg")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import io.github.bucket4j.Bandwidth;
|
||||||
|
import io.github.bucket4j.Bucket;
|
||||||
|
import io.github.bucket4j.ConsumptionProbe;
|
||||||
|
import io.github.bucket4j.Refill;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import stirling.software.SPDF.model.Role;
|
||||||
|
@Component
|
||||||
|
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("rateLimit")
|
||||||
|
public boolean rateLimit;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
if (!rateLimit) {
|
||||||
|
// If rateLimit is not enabled, just pass all requests without rate limiting
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String method = request.getMethod();
|
||||||
|
if (!"POST".equalsIgnoreCase(method)) {
|
||||||
|
// If the request is not a POST, just pass it through without rate limiting
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String identifier = null;
|
||||||
|
|
||||||
|
// Check for API key in the request headers
|
||||||
|
String apiKey = request.getHeader("X-API-Key");
|
||||||
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||||
|
identifier = "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
||||||
|
} else {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
||||||
|
identifier = userDetails.getUsername();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If neither API key nor an authenticated user is present, use IP address
|
||||||
|
if (identifier == null) {
|
||||||
|
identifier = request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
|
||||||
|
Role userRole = getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
||||||
|
|
||||||
|
if (request.getHeader("X-API-Key") != null) {
|
||||||
|
// It's an API call
|
||||||
|
processRequest(userRole.getApiCallsPerDay(), identifier, apiBuckets, request, response, filterChain);
|
||||||
|
} else {
|
||||||
|
// It's a Web UI call
|
||||||
|
processRequest(userRole.getWebCallsPerDay(), identifier, webBuckets, request, response, filterChain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Role getRoleFromAuthentication(Authentication authentication) {
|
||||||
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
for (GrantedAuthority authority : authentication.getAuthorities()) {
|
||||||
|
try {
|
||||||
|
return Role.fromString(authority.getAuthority());
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
// Ignore and continue to next authority.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("User does not have a valid role.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRequest(int limitPerDay, String identifier, Map<String, Bucket> buckets,
|
||||||
|
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay));
|
||||||
|
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
|
||||||
|
|
||||||
|
if (probe.isConsumed()) {
|
||||||
|
response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens()));
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} else {
|
||||||
|
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;
|
||||||
|
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
|
||||||
|
response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill));
|
||||||
|
response.getWriter().write("Rate limit exceeded for POST requests.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bucket createUserBucket(int limitPerDay) {
|
||||||
|
Bandwidth limit = Bandwidth.classic(limitPerDay, Refill.intervally(limitPerDay, Duration.ofDays(1)));
|
||||||
|
return Bucket.builder().addLimit(limit).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.Authority;
|
||||||
|
import stirling.software.SPDF.model.User;
|
||||||
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
@Service
|
||||||
|
public class UserService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public Authentication getAuthentication(String apiKey) {
|
||||||
|
User user = getUserByApiKey(apiKey);
|
||||||
|
if (user == null) {
|
||||||
|
throw new UsernameNotFoundException("API key is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the user into an Authentication object
|
||||||
|
return new UsernamePasswordAuthenticationToken(
|
||||||
|
user, // principal (typically the user)
|
||||||
|
null, // credentials (we don't expose the password or API key here)
|
||||||
|
getAuthorities(user) // user's authorities (roles/permissions)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
|
||||||
|
// Convert each Authority object into a SimpleGrantedAuthority object.
|
||||||
|
return user.getAuthorities().stream()
|
||||||
|
.map((Authority authority) -> new SimpleGrantedAuthority(authority.getAuthority()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateApiKey() {
|
||||||
|
String apiKey;
|
||||||
|
do {
|
||||||
|
apiKey = UUID.randomUUID().toString();
|
||||||
|
} while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User addApiKeyToUser(String username) {
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||||
|
|
||||||
|
user.setApiKey(generateApiKey());
|
||||||
|
return userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User refreshApiKeyForUser(String username) {
|
||||||
|
return addApiKeyToUser(username); // reuse the add API key method for refreshing
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiKeyForUser(String username) {
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||||
|
return user.getApiKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidApiKey(String apiKey) {
|
||||||
|
return userRepository.findByApiKey(apiKey) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User getUserByApiKey(String apiKey) {
|
||||||
|
return userRepository.findByApiKey(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDetails loadUserByApiKey(String apiKey) {
|
||||||
|
User userOptional = userRepository.findByApiKey(apiKey);
|
||||||
|
if (userOptional != null) {
|
||||||
|
User user = userOptional;
|
||||||
|
// Convert your User entity to a UserDetails object with authorities
|
||||||
|
return new org.springframework.security.core.userdetails.User(
|
||||||
|
user.getUsername(),
|
||||||
|
user.getPassword(), // you might not need this for API key auth
|
||||||
|
getAuthorities(user)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null; // or throw an exception
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean validateApiKeyForUser(String username, String apiKey) {
|
||||||
|
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||||
|
return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveUser(String username, String password) {
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(username);
|
||||||
|
user.setPassword(passwordEncoder.encode(password));
|
||||||
|
user.setEnabled(true);
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveUser(String username, String password, String role, boolean firstLogin) {
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(username);
|
||||||
|
user.setPassword(passwordEncoder.encode(password));
|
||||||
|
user.addAuthority(new Authority(role, user));
|
||||||
|
user.setEnabled(true);
|
||||||
|
user.setFirstLogin(firstLogin);
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveUser(String username, String password, String role) {
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(username);
|
||||||
|
user.setPassword(passwordEncoder.encode(password));
|
||||||
|
user.addAuthority(new Authority(role, user));
|
||||||
|
user.setEnabled(true);
|
||||||
|
user.setFirstLogin(false);
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteUser(String username) {
|
||||||
|
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||||
|
if (userOpt.isPresent()) {
|
||||||
|
userRepository.delete(userOpt.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean usernameExists(String username) {
|
||||||
|
return userRepository.findByUsername(username).isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasUsers() {
|
||||||
|
return userRepository.count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateUserSettings(String username, Map<String, String> updates) {
|
||||||
|
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||||
|
if (userOpt.isPresent()) {
|
||||||
|
User user = userOpt.get();
|
||||||
|
Map<String, String> settingsMap = user.getSettings();
|
||||||
|
|
||||||
|
if(settingsMap == null) {
|
||||||
|
settingsMap = new HashMap<String,String>();
|
||||||
|
}
|
||||||
|
settingsMap.clear();
|
||||||
|
settingsMap.putAll(updates);
|
||||||
|
user.setSettings(settingsMap);
|
||||||
|
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> findByUsername(String username) {
|
||||||
|
return userRepository.findByUsername(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeUsername(User user, String newUsername) {
|
||||||
|
user.setUsername(newUsername);
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changePassword(User user, String newPassword) {
|
||||||
|
user.setPassword(passwordEncoder.encode(newPassword));
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeFirstUse(User user, boolean firstUse) {
|
||||||
|
user.setFirstLogin(firstUse);
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isPasswordCorrect(User user, String currentPassword) {
|
||||||
|
return passwordEncoder.matches(currentPassword, user.getPassword());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,132 +1,86 @@
|
||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipOutputStream;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||||
|
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.graphics.form.PDFormXObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
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.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
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 com.itextpdf.kernel.geom.PageSize;
|
|
||||||
import com.itextpdf.kernel.geom.Rectangle;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfPage;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfReader;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.EventType;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener;
|
|
||||||
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
|
|
||||||
|
|
||||||
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.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.general.CropPdfForm;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class CropController {
|
public class CropController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
|
||||||
|
|
||||||
|
|
||||||
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")
|
@Operation(summary = "Crops a PDF document", description = "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(
|
public ResponseEntity<byte[]> cropPdf(@ModelAttribute CropPdfForm form)
|
||||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
throws IOException {
|
||||||
@Parameter(description = "The x-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("x") float x,
|
|
||||||
@Parameter(description = "The y-coordinate of the top-left corner of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("y") float y,
|
|
||||||
@Parameter(description = "The width of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("width") float width,
|
|
||||||
@Parameter(description = "The height of the crop area", required = true, schema = @Schema(type = "number")) @RequestParam("height") float height) throws IOException {
|
|
||||||
byte[] bytes = file.getBytes();
|
|
||||||
System.out.println("x=" + x + ", " + "y=" + y + ", " + "width=" + width + ", " +"height=" + height );
|
|
||||||
PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes));
|
|
||||||
PdfDocument pdfDoc = new PdfDocument(reader);
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
PdfWriter writer = new PdfWriter(baos);
|
|
||||||
PdfDocument outputPdf = new PdfDocument(writer);
|
|
||||||
|
|
||||||
int totalPages = pdfDoc.getNumberOfPages();
|
|
||||||
|
|
||||||
for (int i = 1; i <= totalPages; i++) {
|
|
||||||
PdfPage page = outputPdf.addNewPage(new PageSize(width, height));
|
|
||||||
PdfCanvas pdfCanvas = new PdfCanvas(page);
|
|
||||||
|
|
||||||
PdfFormXObject formXObject = pdfDoc.getPage(i).copyAsFormXObject(outputPdf);
|
|
||||||
|
|
||||||
// Save the graphics state, apply the transformations, add the object, and then
|
|
||||||
// restore the graphics state
|
|
||||||
pdfCanvas.saveState();
|
|
||||||
pdfCanvas.rectangle(x, y, width, height);
|
|
||||||
pdfCanvas.clip();
|
|
||||||
pdfCanvas.addXObject(formXObject, -x, -y);
|
|
||||||
pdfCanvas.restoreState();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
outputPdf.close();
|
|
||||||
byte[] pdfContent = baos.toByteArray();
|
|
||||||
pdfDoc.close();
|
PDDocument sourceDocument = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()));
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfContent,
|
|
||||||
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf");
|
PDDocument newDocument = new PDDocument();
|
||||||
|
|
||||||
|
int totalPages = sourceDocument.getNumberOfPages();
|
||||||
|
|
||||||
|
LayerUtility layerUtility = new LayerUtility(newDocument);
|
||||||
|
|
||||||
|
for (int i = 0; i < totalPages; i++) {
|
||||||
|
PDPage sourcePage = sourceDocument.getPage(i);
|
||||||
|
|
||||||
|
// Create a new page with the size of the source page
|
||||||
|
PDPage newPage = new PDPage(sourcePage.getMediaBox());
|
||||||
|
newDocument.addPage(newPage);
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
|
||||||
|
|
||||||
|
// Import the source page as a form XObject
|
||||||
|
PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);
|
||||||
|
|
||||||
|
contentStream.saveGraphicsState();
|
||||||
|
|
||||||
|
// Define the crop area
|
||||||
|
contentStream.addRect(form.getX(), form.getY(), form.getWidth(), form.getHeight());
|
||||||
|
contentStream.clip();
|
||||||
|
|
||||||
|
// Draw the entire formXObject
|
||||||
|
contentStream.drawForm(formXObject);
|
||||||
|
|
||||||
|
contentStream.restoreGraphicsState();
|
||||||
|
|
||||||
|
contentStream.close();
|
||||||
|
|
||||||
|
// Now, set the new page's media box to the cropped size
|
||||||
|
newPage.setMediaBox(new PDRectangle(form.getX(), form.getY(), form.getWidth(), form.getHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
newDocument.save(baos);
|
||||||
|
newDocument.close();
|
||||||
|
sourceDocument.close();
|
||||||
|
|
||||||
|
byte[] pdfContent = baos.toByteArray();
|
||||||
|
return WebResponseUtils.bytesToWebResponse(pdfContent, form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +1,115 @@
|
||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
|
||||||
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;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
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.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class MergeController {
|
public class MergeController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
|
||||||
|
|
||||||
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
|
||||||
// Create a new empty document
|
|
||||||
PDDocument mergedDoc = new PDDocument();
|
|
||||||
|
|
||||||
// Iterate over the list of documents and add their pages to the merged document
|
private PDDocument mergeDocuments(List<PDDocument> documents) throws IOException {
|
||||||
|
PDDocument mergedDoc = new PDDocument();
|
||||||
for (PDDocument doc : documents) {
|
for (PDDocument doc : documents) {
|
||||||
// Get all pages from the current document
|
for (PDPage page : doc.getPages()) {
|
||||||
PDPageTree pages = doc.getPages();
|
|
||||||
// Iterate over the pages and add them to the merged document
|
|
||||||
for (PDPage page : pages) {
|
|
||||||
mergedDoc.addPage(page);
|
mergedDoc.addPage(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the merged document
|
|
||||||
return mergedDoc;
|
return mergedDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Comparator<MultipartFile> getSortComparator(String sortType) {
|
||||||
|
switch (sortType) {
|
||||||
|
case "byFileName":
|
||||||
|
return Comparator.comparing(MultipartFile::getOriginalFilename);
|
||||||
|
case "byDateModified":
|
||||||
|
return (file1, file2) -> {
|
||||||
|
try {
|
||||||
|
BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class);
|
||||||
|
BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class);
|
||||||
|
return attr1.lastModifiedTime().compareTo(attr2.lastModifiedTime());
|
||||||
|
} catch (IOException e) {
|
||||||
|
return 0; // If there's an error, treat them as equal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case "byDateCreated":
|
||||||
|
return (file1, file2) -> {
|
||||||
|
try {
|
||||||
|
BasicFileAttributes attr1 = Files.readAttributes(Paths.get(file1.getOriginalFilename()), BasicFileAttributes.class);
|
||||||
|
BasicFileAttributes attr2 = Files.readAttributes(Paths.get(file2.getOriginalFilename()), BasicFileAttributes.class);
|
||||||
|
return attr1.creationTime().compareTo(attr2.creationTime());
|
||||||
|
} catch (IOException e) {
|
||||||
|
return 0; // If there's an error, treat them as equal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case "byPDFTitle":
|
||||||
|
return (file1, file2) -> {
|
||||||
|
try (PDDocument doc1 = PDDocument.load(file1.getInputStream());
|
||||||
|
PDDocument doc2 = PDDocument.load(file2.getInputStream())) {
|
||||||
|
String title1 = doc1.getDocumentInformation().getTitle();
|
||||||
|
String title2 = doc2.getDocumentInformation().getTitle();
|
||||||
|
return title1.compareTo(title2);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case "orderProvided":
|
||||||
|
default:
|
||||||
|
return (file1, file2) -> 0; // Default is the order provided
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
||||||
|
@Operation(summary = "Merge multiple PDF files into one",
|
||||||
|
description = "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) throws IOException {
|
||||||
|
|
||||||
|
MultipartFile[] files = form.getFileInput();
|
||||||
|
Arrays.sort(files, getSortComparator(form.getSortType()));
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
|
||||||
@Operation(
|
|
||||||
summary = "Merge multiple PDF files into one",
|
|
||||||
description = "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(
|
|
||||||
@RequestPart(required = true, value = "fileInput")
|
|
||||||
@Parameter(description = "The input PDF files to be merged into a single file", required = true)
|
|
||||||
MultipartFile[] files) throws IOException {
|
|
||||||
// Read the input PDF files into PDDocument objects
|
|
||||||
List<PDDocument> documents = new ArrayList<>();
|
List<PDDocument> documents = new ArrayList<>();
|
||||||
|
|
||||||
// Loop through the files array and read each file into a PDDocument
|
|
||||||
for (MultipartFile file : files) {
|
for (MultipartFile file : files) {
|
||||||
documents.add(PDDocument.load(file.getInputStream()));
|
try (InputStream is = file.getInputStream()) {
|
||||||
|
documents.add(PDDocument.load(is));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PDDocument mergedDoc = mergeDocuments(documents);
|
try (PDDocument mergedDoc = mergeDocuments(documents)) {
|
||||||
|
|
||||||
|
|
||||||
// Return the merged PDF as a response
|
|
||||||
ResponseEntity<byte[]> response = WebResponseUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
|
ResponseEntity<byte[]> response = WebResponseUtils.pdfDocToWebResponse(mergedDoc, files[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_merged.pdf");
|
||||||
|
return response;
|
||||||
|
} finally {
|
||||||
for (PDDocument doc : documents) {
|
for (PDDocument doc : documents) {
|
||||||
// Close the document after processing
|
if (doc != null) {
|
||||||
doc.close();
|
doc.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,101 +1,124 @@
|
||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
|
import java.awt.Color;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||||
|
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.graphics.form.PDFormXObject;
|
||||||
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
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 com.itextpdf.kernel.geom.PageSize;
|
|
||||||
import com.itextpdf.kernel.geom.Rectangle;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfPage;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfReader;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
|
||||||
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class MultiPageLayoutController {
|
public class MultiPageLayoutController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
|
||||||
|
|
||||||
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Merge multiple pages of a PDF document into a single page", description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO")
|
@Operation(
|
||||||
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
|
summary = "Merge multiple pages of a PDF document into a single page",
|
||||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
description = "This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO"
|
||||||
@Parameter(description = "The number of pages to fit onto a single sheet in the output PDF. Acceptable values are 2, 3, 4, 9, 16.", required = true, schema = @Schema(type = "integer", allowableValues = {
|
)
|
||||||
"2", "3", "4", "9", "16" })) @RequestParam("pagesPerSheet") int pagesPerSheet)
|
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(@ModelAttribute MergeMultiplePagesRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
if (pagesPerSheet != 2 && pagesPerSheet != 3
|
int pagesPerSheet = request.getPagesPerSheet();
|
||||||
&& pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) {
|
MultipartFile file = request.getFileInput();
|
||||||
|
boolean addBorder = request.isAddBorder();
|
||||||
|
|
||||||
|
if (pagesPerSheet != 2 && pagesPerSheet != 3 && pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) {
|
||||||
throw new IllegalArgumentException("pagesPerSheet must be 2, 3 or a perfect square");
|
throw new IllegalArgumentException("pagesPerSheet must be 2, 3 or a perfect square");
|
||||||
}
|
}
|
||||||
|
|
||||||
int cols = pagesPerSheet == 2 || pagesPerSheet == 3 ? pagesPerSheet : (int) Math.sqrt(pagesPerSheet);
|
int cols = pagesPerSheet == 2 || pagesPerSheet == 3 ? 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);
|
||||||
|
|
||||||
byte[] bytes = file.getBytes();
|
PDDocument sourceDocument = PDDocument.load(file.getInputStream());
|
||||||
PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes));
|
PDDocument newDocument = new PDDocument();
|
||||||
PdfDocument pdfDoc = new PdfDocument(reader);
|
PDPage newPage = new PDPage(PDRectangle.A4);
|
||||||
|
newDocument.addPage(newPage);
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
int totalPages = sourceDocument.getNumberOfPages();
|
||||||
PdfWriter writer = new PdfWriter(baos);
|
float cellWidth = newPage.getMediaBox().getWidth() / cols;
|
||||||
PdfDocument outputPdf = new PdfDocument(writer);
|
float cellHeight = newPage.getMediaBox().getHeight() / rows;
|
||||||
PageSize pageSize = new PageSize(PageSize.A4.rotate());
|
|
||||||
|
|
||||||
int totalPages = pdfDoc.getNumberOfPages();
|
PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
float cellWidth = pageSize.getWidth() / cols;
|
LayerUtility layerUtility = new LayerUtility(newDocument);
|
||||||
float cellHeight = pageSize.getHeight() / rows;
|
|
||||||
|
|
||||||
for (int i = 1; i <= totalPages; i += pagesPerSheet) {
|
float borderThickness = 1.5f; // Specify border thickness as required
|
||||||
PdfPage page = outputPdf.addNewPage(pageSize);
|
contentStream.setLineWidth(borderThickness);
|
||||||
PdfCanvas pdfCanvas = new PdfCanvas(page);
|
contentStream.setStrokingColor(Color.BLACK);
|
||||||
|
|
||||||
for (int row = 0; row < rows; row++) {
|
for (int i = 0; i < totalPages; i++) {
|
||||||
for (int col = 0; col < cols; col++) {
|
if (i != 0 && i % pagesPerSheet == 0) {
|
||||||
int index = i + row * cols + col;
|
// Close the current content stream and create a new page and content stream
|
||||||
if (index <= totalPages) {
|
contentStream.close();
|
||||||
// Get the page and calculate scaling factors
|
newPage = new PDPage(PDRectangle.A4);
|
||||||
Rectangle rect = pdfDoc.getPage(index).getPageSize();
|
newDocument.addPage(newPage);
|
||||||
|
contentStream = new PDPageContentStream(newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
PDPage sourcePage = sourceDocument.getPage(i);
|
||||||
|
PDRectangle rect = sourcePage.getMediaBox();
|
||||||
float scaleWidth = cellWidth / rect.getWidth();
|
float scaleWidth = cellWidth / rect.getWidth();
|
||||||
float scaleHeight = cellHeight / rect.getHeight();
|
float scaleHeight = cellHeight / rect.getHeight();
|
||||||
float scale = Math.min(scaleWidth, scaleHeight);
|
float scale = Math.min(scaleWidth, scaleHeight);
|
||||||
|
|
||||||
PdfFormXObject formXObject = pdfDoc.getPage(index).copyAsFormXObject(outputPdf);
|
int adjustedPageIndex = i % pagesPerSheet; // This will reset the index for every new page
|
||||||
float x = col * cellWidth + (cellWidth - rect.getWidth() * scale) / 2;
|
int rowIndex = adjustedPageIndex / cols;
|
||||||
float y = (rows - 1 - row) * cellHeight + (cellHeight - rect.getHeight() * scale) / 2;
|
int colIndex = adjustedPageIndex % cols;
|
||||||
|
|
||||||
// Save the graphics state, apply the transformations, add the object, and then
|
float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2;
|
||||||
// restore the graphics state
|
float y = newPage.getMediaBox().getHeight() - ((rowIndex + 1) * cellHeight - (cellHeight - rect.getHeight() * scale) / 2);
|
||||||
pdfCanvas.saveState();
|
|
||||||
pdfCanvas.concatMatrix(scale, 0, 0, scale, x, y);
|
contentStream.saveGraphicsState();
|
||||||
pdfCanvas.addXObject(formXObject, 0, 0);
|
contentStream.transform(Matrix.getTranslateInstance(x, y));
|
||||||
pdfCanvas.restoreState();
|
contentStream.transform(Matrix.getScaleInstance(scale, scale));
|
||||||
}
|
|
||||||
}
|
PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);
|
||||||
|
contentStream.drawForm(formXObject);
|
||||||
|
|
||||||
|
contentStream.restoreGraphicsState();
|
||||||
|
|
||||||
|
if(addBorder) {
|
||||||
|
// Draw border around each page
|
||||||
|
float borderX = colIndex * cellWidth;
|
||||||
|
float borderY = newPage.getMediaBox().getHeight() - (rowIndex + 1) * cellHeight;
|
||||||
|
contentStream.addRect(borderX, borderY, cellWidth, cellHeight);
|
||||||
|
contentStream.stroke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outputPdf.close();
|
|
||||||
byte[] pdfContent = baos.toByteArray();
|
|
||||||
pdfDoc.close();
|
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfContent, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf");
|
contentStream.close(); // Close the final content stream
|
||||||
|
sourceDocument.close();
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
newDocument.save(baos);
|
||||||
|
newDocument.close();
|
||||||
|
|
||||||
|
byte[] result = baos.toByteArray();
|
||||||
|
return WebResponseUtils.bytesToWebResponse(result, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_layoutChanged.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
@ -17,12 +19,14 @@ import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.SortTypes;
|
||||||
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
|
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class RearrangePagesPDFController {
|
public class RearrangePagesPDFController {
|
||||||
|
|
||||||
|
@ -30,11 +34,12 @@ public class RearrangePagesPDFController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
||||||
@Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF Type:SISO")
|
@Operation(summary = "Remove pages from a PDF file", description = "This endpoint removes specified pages from a given PDF file. Users can provide a comma-separated list of page numbers or ranges to delete. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> deletePages(
|
public ResponseEntity<byte[]> deletePages(@ModelAttribute PDFWithPageNums request )
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which pages will be removed") MultipartFile pdfFile,
|
|
||||||
@RequestParam("pagesToDelete") @Parameter(description = "Comma-separated list of pages or page ranges to delete, e.g., '1,3,5-8'") String pagesToDelete)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
|
String pagesToDelete = request.getPageNumbers();
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(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
|
||||||
|
@ -51,9 +56,7 @@ public class RearrangePagesPDFController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum CustomMode {
|
|
||||||
REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, ODD_EVEN_SPLIT, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST,
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Integer> removeFirst(int totalPages) {
|
private List<Integer> removeFirst(int totalPages) {
|
||||||
if (totalPages <= 1)
|
if (totalPages <= 1)
|
||||||
|
@ -114,6 +117,18 @@ public class RearrangePagesPDFController {
|
||||||
return newPageOrder;
|
return newPageOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Integer> sideStitchBooklet(int totalPages) {
|
||||||
|
List<Integer> newPageOrder = new ArrayList<>();
|
||||||
|
for (int i = 0; i < (totalPages + 3) / 4; i++) {
|
||||||
|
int begin = i * 4;
|
||||||
|
newPageOrder.add(Math.min(begin + 3, totalPages - 1));
|
||||||
|
newPageOrder.add(Math.min(begin, totalPages - 1));
|
||||||
|
newPageOrder.add(Math.min(begin + 1, totalPages - 1));
|
||||||
|
newPageOrder.add(Math.min(begin + 2, totalPages - 1));
|
||||||
|
}
|
||||||
|
return newPageOrder;
|
||||||
|
}
|
||||||
|
|
||||||
private List<Integer> oddEvenSplit(int totalPages) {
|
private List<Integer> oddEvenSplit(int totalPages) {
|
||||||
List<Integer> newPageOrder = new ArrayList<>();
|
List<Integer> newPageOrder = new ArrayList<>();
|
||||||
for (int i = 1; i <= totalPages; i += 2) {
|
for (int i = 1; i <= totalPages; i += 2) {
|
||||||
|
@ -125,9 +140,9 @@ public class RearrangePagesPDFController {
|
||||||
return newPageOrder;
|
return newPageOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Integer> processCustomMode(String customMode, int totalPages) {
|
private List<Integer> processSortTypes(String sortTypes, int totalPages) {
|
||||||
try {
|
try {
|
||||||
CustomMode mode = CustomMode.valueOf(customMode.toUpperCase());
|
SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase());
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case REVERSE_ORDER:
|
case REVERSE_ORDER:
|
||||||
return reverseOrder(totalPages);
|
return reverseOrder(totalPages);
|
||||||
|
@ -135,6 +150,8 @@ public class RearrangePagesPDFController {
|
||||||
return duplexSort(totalPages);
|
return duplexSort(totalPages);
|
||||||
case BOOKLET_SORT:
|
case BOOKLET_SORT:
|
||||||
return bookletSort(totalPages);
|
return bookletSort(totalPages);
|
||||||
|
case SIDE_STITCH_BOOKLET_SORT:
|
||||||
|
return sideStitchBooklet(totalPages);
|
||||||
case ODD_EVEN_SPLIT:
|
case ODD_EVEN_SPLIT:
|
||||||
return oddEvenSplit(totalPages);
|
return oddEvenSplit(totalPages);
|
||||||
case REMOVE_FIRST:
|
case REMOVE_FIRST:
|
||||||
|
@ -154,16 +171,10 @@ public class RearrangePagesPDFController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
||||||
@Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode. Input:PDF Output:PDF")
|
@Operation(summary = "Rearrange pages in a PDF file", description = "This endpoint rearranges pages in a given PDF file based on the specified page order or custom mode. Users can provide a page order as a comma-separated list of page numbers or page ranges, or a custom mode. Input:PDF Output:PDF")
|
||||||
public ResponseEntity<byte[]> rearrangePages(
|
public ResponseEntity<byte[]> rearrangePages(@ModelAttribute RearrangePagesRequest request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to rearrange pages") MultipartFile pdfFile,
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
@RequestParam(required = false, value = "pageOrder") @Parameter(description = "The new page order as a comma-separated list of page numbers, page ranges (e.g., '1,3,5-7'), or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')") String pageOrder,
|
String pageOrder = request.getPageNumbers();
|
||||||
@RequestParam(required = false, value = "customMode") @Parameter(schema = @Schema(implementation = CustomMode.class, description = "The custom mode for page rearrangement. "
|
String sortType = request.getCustomMode();
|
||||||
+ "Valid values are:\n" + "REVERSE_ORDER: Reverses the order of all pages.\n"
|
|
||||||
+ "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). "
|
|
||||||
+ "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n"
|
|
||||||
+ "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n"
|
|
||||||
+ "REMOVE_FIRST: Removes the first page.\n" + "REMOVE_LAST: Removes the last page.\n"
|
|
||||||
+ "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n")) String customMode) {
|
|
||||||
try {
|
try {
|
||||||
// Load the input PDF
|
// Load the input PDF
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||||
|
@ -171,15 +182,14 @@ public class RearrangePagesPDFController {
|
||||||
// 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];
|
||||||
int totalPages = document.getNumberOfPages();
|
int totalPages = document.getNumberOfPages();
|
||||||
System.out.println("pageOrder=" + pageOrder);
|
|
||||||
System.out.println("customMode length =" + customMode.length());
|
|
||||||
List<Integer> newPageOrder;
|
List<Integer> newPageOrder;
|
||||||
if (customMode != null && customMode.length() > 0) {
|
if (sortType != null && sortType.length() > 0) {
|
||||||
newPageOrder = processCustomMode(customMode, totalPages);
|
newPageOrder = processSortTypes(sortType, totalPages);
|
||||||
} else {
|
} else {
|
||||||
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages);
|
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages);
|
||||||
}
|
}
|
||||||
|
logger.info("newPageOrder = " +newPageOrder);
|
||||||
|
logger.info("totalPages = " +totalPages);
|
||||||
// Create a new list to hold the pages in the new order
|
// Create a new list to hold the pages in the new order
|
||||||
List<PDPage> newPages = new ArrayList<>();
|
List<PDPage> newPages = new ArrayList<>();
|
||||||
for (int i = 0; i < newPageOrder.size(); i++) {
|
for (int i = 0; i < newPageOrder.size(); i++) {
|
||||||
|
|
|
@ -8,18 +8,19 @@ import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.general.RotatePDFRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class RotationController {
|
public class RotationController {
|
||||||
|
|
||||||
|
@ -31,13 +32,9 @@ public class RotationController {
|
||||||
description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF Type:SISO"
|
description = "This endpoint rotates a given PDF file by a specified angle. The angle must be a multiple of 90. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> rotatePDF(
|
public ResponseEntity<byte[]> rotatePDF(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@ModelAttribute RotatePDFRequest request) throws IOException {
|
||||||
@Parameter(description = "The PDF file to be rotated", required = true)
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
MultipartFile pdfFile,
|
Integer angle = request.getAngle();
|
||||||
@RequestParam("angle")
|
|
||||||
@Parameter(description = "The angle by which to rotate the PDF file. This should be a multiple of 90.", example = "90", required = true)
|
|
||||||
Integer angle) throws IOException {
|
|
||||||
|
|
||||||
// Load the PDF document
|
// Load the PDF document
|
||||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
||||||
|
|
||||||
|
|
|
@ -1,48 +1,32 @@
|
||||||
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 java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
import org.apache.pdfbox.multipdf.LayerUtility;
|
||||||
|
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.graphics.form.PDFormXObject;
|
||||||
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
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.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
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 com.itextpdf.kernel.geom.PageSize;
|
|
||||||
import com.itextpdf.kernel.geom.Rectangle;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfPage;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfReader;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.EventType;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.data.TextRenderInfo;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener;
|
|
||||||
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
|
|
||||||
|
|
||||||
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.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.general.ScalePagesRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class ScalePagesController {
|
public class ScalePagesController {
|
||||||
|
|
||||||
|
@ -50,194 +34,77 @@ public class ScalePagesController {
|
||||||
|
|
||||||
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF Type:SISO")
|
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> scalePages(
|
public ResponseEntity<byte[]> scalePages(@ModelAttribute ScalePagesRequest request) throws IOException {
|
||||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
MultipartFile file = request.getFileInput();
|
||||||
@Parameter(description = "The scale of pages in the output PDF. Acceptable values are A0-A10, B0-B9, LETTER, TABLOID, LEDGER, LEGAL, EXECUTIVE.", required = true, schema = @Schema(type = "string", allowableValues = {
|
String targetPDRectangle = request.getPageSize();
|
||||||
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "B0", "B1", "B2", "B3", "B4",
|
float scaleFactor = request.getScaleFactor();
|
||||||
"B5", "B6", "B7", "B8", "B9", "LETTER", "TABLOID", "LEDGER", "LEGAL",
|
|
||||||
"EXECUTIVE" })) @RequestParam("pageSize") String targetPageSize,
|
|
||||||
@Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "integer")) @RequestParam("scaleFactor") float scaleFactor)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
Map<String, PageSize> sizeMap = new HashMap<>();
|
Map<String, PDRectangle> sizeMap = new HashMap<>();
|
||||||
// Add A0 - A10
|
// Add A0 - A10
|
||||||
sizeMap.put("A0", PageSize.A0);
|
sizeMap.put("A0", PDRectangle.A0);
|
||||||
sizeMap.put("A1", PageSize.A1);
|
sizeMap.put("A1", PDRectangle.A1);
|
||||||
sizeMap.put("A2", PageSize.A2);
|
sizeMap.put("A2", PDRectangle.A2);
|
||||||
sizeMap.put("A3", PageSize.A3);
|
sizeMap.put("A3", PDRectangle.A3);
|
||||||
sizeMap.put("A4", PageSize.A4);
|
sizeMap.put("A4", PDRectangle.A4);
|
||||||
sizeMap.put("A5", PageSize.A5);
|
sizeMap.put("A5", PDRectangle.A5);
|
||||||
sizeMap.put("A6", PageSize.A6);
|
sizeMap.put("A6", PDRectangle.A6);
|
||||||
sizeMap.put("A7", PageSize.A7);
|
|
||||||
sizeMap.put("A8", PageSize.A8);
|
|
||||||
sizeMap.put("A9", PageSize.A9);
|
|
||||||
sizeMap.put("A10", PageSize.A10);
|
|
||||||
// Add B0 - B9
|
|
||||||
sizeMap.put("B0", PageSize.B0);
|
|
||||||
sizeMap.put("B1", PageSize.B1);
|
|
||||||
sizeMap.put("B2", PageSize.B2);
|
|
||||||
sizeMap.put("B3", PageSize.B3);
|
|
||||||
sizeMap.put("B4", PageSize.B4);
|
|
||||||
sizeMap.put("B5", PageSize.B5);
|
|
||||||
sizeMap.put("B6", PageSize.B6);
|
|
||||||
sizeMap.put("B7", PageSize.B7);
|
|
||||||
sizeMap.put("B8", PageSize.B8);
|
|
||||||
sizeMap.put("B9", PageSize.B9);
|
|
||||||
// Add other sizes
|
|
||||||
sizeMap.put("LETTER", PageSize.LETTER);
|
|
||||||
sizeMap.put("TABLOID", PageSize.TABLOID);
|
|
||||||
sizeMap.put("LEDGER", PageSize.LEDGER);
|
|
||||||
sizeMap.put("LEGAL", PageSize.LEGAL);
|
|
||||||
sizeMap.put("EXECUTIVE", PageSize.EXECUTIVE);
|
|
||||||
|
|
||||||
if (!sizeMap.containsKey(targetPageSize)) {
|
// Add other sizes
|
||||||
|
sizeMap.put("LETTER", PDRectangle.LETTER);
|
||||||
|
sizeMap.put("LEGAL", PDRectangle.LEGAL);
|
||||||
|
|
||||||
|
if (!sizeMap.containsKey(targetPDRectangle)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Invalid pageSize. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10");
|
"Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10");
|
||||||
}
|
}
|
||||||
|
|
||||||
PageSize pageSize = sizeMap.get(targetPageSize);
|
PDRectangle targetSize = sizeMap.get(targetPDRectangle);
|
||||||
|
|
||||||
|
PDDocument sourceDocument = PDDocument.load(file.getBytes());
|
||||||
|
PDDocument outputDocument = new PDDocument();
|
||||||
|
|
||||||
|
int totalPages = sourceDocument.getNumberOfPages();
|
||||||
|
for (int i = 0; i < totalPages; i++) {
|
||||||
|
PDPage sourcePage = sourceDocument.getPage(i);
|
||||||
|
PDRectangle sourceSize = sourcePage.getMediaBox();
|
||||||
|
|
||||||
|
float scaleWidth = targetSize.getWidth() / sourceSize.getWidth();
|
||||||
|
float scaleHeight = targetSize.getHeight() / sourceSize.getHeight();
|
||||||
|
float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor;
|
||||||
|
|
||||||
|
PDPage newPage = new PDPage(targetSize);
|
||||||
|
outputDocument.addPage(newPage);
|
||||||
|
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(outputDocument, newPage, PDPageContentStream.AppendMode.APPEND, true);
|
||||||
|
|
||||||
|
float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2;
|
||||||
|
float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2;
|
||||||
|
|
||||||
|
contentStream.saveGraphicsState();
|
||||||
|
contentStream.transform(Matrix.getTranslateInstance(x, y));
|
||||||
|
contentStream.transform(Matrix.getScaleInstance(scale, scale));
|
||||||
|
|
||||||
|
LayerUtility layerUtility = new LayerUtility(outputDocument);
|
||||||
|
PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, i);
|
||||||
|
contentStream.drawForm(form);
|
||||||
|
|
||||||
|
contentStream.restoreGraphicsState();
|
||||||
|
contentStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
byte[] bytes = file.getBytes();
|
|
||||||
PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes));
|
|
||||||
PdfDocument pdfDoc = new PdfDocument(reader);
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
PdfWriter writer = new PdfWriter(baos);
|
outputDocument.save(baos);
|
||||||
PdfDocument outputPdf = new PdfDocument(writer);
|
outputDocument.close();
|
||||||
|
sourceDocument.close();
|
||||||
|
|
||||||
int totalPages = pdfDoc.getNumberOfPages();
|
|
||||||
|
|
||||||
for (int i = 1; i <= totalPages; i++) {
|
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(),
|
||||||
PdfPage page = outputPdf.addNewPage(pageSize);
|
|
||||||
PdfCanvas pdfCanvas = new PdfCanvas(page);
|
|
||||||
|
|
||||||
// Get the page and calculate scaling factors
|
|
||||||
Rectangle rect = pdfDoc.getPage(i).getPageSize();
|
|
||||||
float scaleWidth = pageSize.getWidth() / rect.getWidth();
|
|
||||||
float scaleHeight = pageSize.getHeight() / rect.getHeight();
|
|
||||||
float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor;
|
|
||||||
System.out.println("Scale: " + scale);
|
|
||||||
|
|
||||||
PdfFormXObject formXObject = pdfDoc.getPage(i).copyAsFormXObject(outputPdf);
|
|
||||||
float x = (pageSize.getWidth() - rect.getWidth() * scale) / 2; // Center Page
|
|
||||||
float y = (pageSize.getHeight() - rect.getHeight() * scale) / 2;
|
|
||||||
|
|
||||||
// Save the graphics state, apply the transformations, add the object, and then
|
|
||||||
// restore the graphics state
|
|
||||||
pdfCanvas.saveState();
|
|
||||||
pdfCanvas.concatMatrix(scale, 0, 0, scale, x, y);
|
|
||||||
pdfCanvas.addXObject(formXObject, 0, 0);
|
|
||||||
pdfCanvas.restoreState();
|
|
||||||
}
|
|
||||||
|
|
||||||
outputPdf.close();
|
|
||||||
byte[] pdfContent = baos.toByteArray();
|
|
||||||
pdfDoc.close();
|
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfContent,
|
|
||||||
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf");
|
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_scaled.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
|
||||||
@Hidden
|
|
||||||
@PostMapping(value = "/auto-crop", consumes = "multipart/form-data")
|
|
||||||
public ResponseEntity<byte[]> cropPdf(@RequestParam("fileInput") MultipartFile file) throws IOException {
|
|
||||||
byte[] bytes = file.getBytes();
|
|
||||||
PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes));
|
|
||||||
PdfDocument pdfDoc = new PdfDocument(reader);
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
PdfWriter writer = new PdfWriter(baos);
|
|
||||||
PdfDocument outputPdf = new PdfDocument(writer);
|
|
||||||
|
|
||||||
int totalPages = pdfDoc.getNumberOfPages();
|
|
||||||
for (int i = 1; i <= totalPages; i++) {
|
|
||||||
PdfPage page = pdfDoc.getPage(i);
|
|
||||||
Rectangle originalMediaBox = page.getMediaBox();
|
|
||||||
|
|
||||||
Rectangle contentBox = determineContentBox(page);
|
|
||||||
|
|
||||||
// Make sure we don't go outside the original media box.
|
|
||||||
Rectangle intersection = originalMediaBox.getIntersection(contentBox);
|
|
||||||
page.setCropBox(intersection);
|
|
||||||
|
|
||||||
// Copy page to the new document
|
|
||||||
outputPdf.addPage(page.copyTo(outputPdf));
|
|
||||||
}
|
|
||||||
|
|
||||||
outputPdf.close();
|
|
||||||
byte[] pdfContent = baos.toByteArray();
|
|
||||||
pdfDoc.close();
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""
|
|
||||||
+ file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_cropped.pdf\"")
|
|
||||||
.contentType(MediaType.APPLICATION_PDF).body(pdfContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Rectangle determineContentBox(PdfPage page) {
|
|
||||||
// Extract the text from the page and find the bounding box.
|
|
||||||
TextBoundingRectangleFinder finder = new TextBoundingRectangleFinder();
|
|
||||||
PdfCanvasProcessor processor = new PdfCanvasProcessor(finder);
|
|
||||||
processor.processPageContent(page);
|
|
||||||
return finder.getBoundingBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TextBoundingRectangleFinder implements IEventListener {
|
|
||||||
private List<Rectangle> allTextBoxes = new ArrayList<>();
|
|
||||||
|
|
||||||
public Rectangle getBoundingBox() {
|
|
||||||
// Sort the text boxes based on their vertical position
|
|
||||||
allTextBoxes.sort(Comparator.comparingDouble(Rectangle::getTop));
|
|
||||||
|
|
||||||
// Consider a box an outlier if its top is more than 1.5 times the IQR above the
|
|
||||||
// third quartile.
|
|
||||||
int q1Index = allTextBoxes.size() / 4;
|
|
||||||
int q3Index = 3 * allTextBoxes.size() / 4;
|
|
||||||
double iqr = allTextBoxes.get(q3Index).getTop() - allTextBoxes.get(q1Index).getTop();
|
|
||||||
double threshold = allTextBoxes.get(q3Index).getTop() + 1.5 * iqr;
|
|
||||||
|
|
||||||
// Initialize boundingBox to the first non-outlier box
|
|
||||||
int i = 0;
|
|
||||||
while (i < allTextBoxes.size() && allTextBoxes.get(i).getTop() > threshold) {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
if (i == allTextBoxes.size()) {
|
|
||||||
// If all boxes are outliers, just return the first one
|
|
||||||
return allTextBoxes.get(0);
|
|
||||||
}
|
|
||||||
Rectangle boundingBox = allTextBoxes.get(i);
|
|
||||||
|
|
||||||
// Extend the bounding box to include all non-outlier boxes
|
|
||||||
for (; i < allTextBoxes.size(); i++) {
|
|
||||||
Rectangle textBoundingBox = allTextBoxes.get(i);
|
|
||||||
if (textBoundingBox.getTop() > threshold) {
|
|
||||||
// This box is an outlier, skip it
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
float left = Math.min(boundingBox.getLeft(), textBoundingBox.getLeft());
|
|
||||||
float bottom = Math.min(boundingBox.getBottom(), textBoundingBox.getBottom());
|
|
||||||
float right = Math.max(boundingBox.getRight(), textBoundingBox.getRight());
|
|
||||||
float top = Math.max(boundingBox.getTop(), textBoundingBox.getTop());
|
|
||||||
|
|
||||||
// Add a small padding around the bounding box
|
|
||||||
float padding = 10;
|
|
||||||
boundingBox = new Rectangle(left - padding, bottom - padding, right - left + 2 * padding,
|
|
||||||
top - bottom + 2 * padding);
|
|
||||||
}
|
|
||||||
return boundingBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(IEventData data, EventType type) {
|
|
||||||
if (type == EventType.RENDER_TEXT) {
|
|
||||||
TextRenderInfo renderInfo = (TextRenderInfo) data;
|
|
||||||
allTextBoxes.add(renderInfo.getBaseline().getBoundingRectangle());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<EventType> getSupportedEvents() {
|
|
||||||
return Collections.singleton(EventType.RENDER_TEXT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,19 +17,19 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
import org.apache.pdfbox.multipdf.Splitter;
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class SplitPDFController {
|
public class SplitPDFController {
|
||||||
|
|
||||||
|
@ -38,57 +38,36 @@ public class SplitPDFController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
||||||
@Operation(summary = "Split a PDF file into separate documents",
|
@Operation(summary = "Split a PDF file into separate documents",
|
||||||
description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
|
description = "This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
|
||||||
public ResponseEntity<byte[]> splitPdf(
|
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile file = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file to be split")
|
String pages = request.getPageNumbers();
|
||||||
MultipartFile file,
|
|
||||||
@RequestParam("pages")
|
|
||||||
@Parameter(description = "The pages to be included in separate documents. Specify individual page numbers (e.g., '1,3,5'), ranges (e.g., '1-3,5-7'), or 'all' for every page.")
|
|
||||||
String pages) throws IOException {
|
|
||||||
// parse user input
|
|
||||||
|
|
||||||
// open the pdf document
|
// open the pdf document
|
||||||
InputStream inputStream = file.getInputStream();
|
InputStream inputStream = file.getInputStream();
|
||||||
PDDocument document = PDDocument.load(inputStream);
|
PDDocument document = PDDocument.load(inputStream);
|
||||||
|
|
||||||
List<Integer> pageNumbers = new ArrayList<>();
|
List<Integer> pageNumbers = request.getPageNumbersList(document);
|
||||||
pages = pages.replaceAll("\\s+", ""); // remove whitespaces
|
if(!pageNumbers.contains(document.getNumberOfPages() - 1))
|
||||||
if (pages.toLowerCase().equals("all")) {
|
pageNumbers.add(document.getNumberOfPages()- 1);
|
||||||
for (int i = 0; i < document.getNumberOfPages(); i++) {
|
|
||||||
pageNumbers.add(i);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String[] splitPoints = pages.split(",");
|
|
||||||
for (String splitPoint : splitPoints) {
|
|
||||||
List<Integer> orderedPages = GeneralUtils.parsePageList(new String[] {splitPoint}, document.getNumberOfPages());
|
|
||||||
pageNumbers.addAll(orderedPages);
|
|
||||||
}
|
|
||||||
// Add the last page as a split point
|
|
||||||
pageNumbers.add(document.getNumberOfPages() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
logger.info("Splitting PDF into pages: {}", pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||||
|
|
||||||
// split the document
|
Splitter splitter = new Splitter();
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
int previousPageNumber = 0;
|
|
||||||
|
int previousPageNumber = 1; // PDFBox uses 1-based indexing for pages.
|
||||||
for (int splitPoint : pageNumbers) {
|
for (int splitPoint : pageNumbers) {
|
||||||
try (PDDocument splitDocument = new PDDocument()) {
|
splitPoint = splitPoint + 1;
|
||||||
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
splitter.setStartPage(previousPageNumber);
|
||||||
PDPage page = document.getPage(i);
|
splitter.setEndPage(splitPoint);
|
||||||
splitDocument.addPage(page);
|
List<PDDocument> splitDocuments = splitter.split(document);
|
||||||
logger.debug("Adding page {} to split document", i);
|
|
||||||
}
|
|
||||||
previousPageNumber = splitPoint + 1;
|
|
||||||
|
|
||||||
|
for (PDDocument splitDoc : splitDocuments) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
splitDocument.save(baos);
|
splitDoc.save(baos);
|
||||||
|
|
||||||
splitDocumentsBoas.add(baos);
|
splitDocumentsBoas.add(baos);
|
||||||
} catch (Exception e) {
|
splitDoc.close();
|
||||||
logger.error("Failed splitting documents and saving them", e);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previousPageNumber = splitPoint + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,29 @@
|
||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import com.itextpdf.kernel.pdf.*;
|
import java.io.IOException;
|
||||||
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
|
|
||||||
import com.itextpdf.kernel.geom.PageSize;
|
|
||||||
import com.itextpdf.kernel.geom.Rectangle;
|
|
||||||
import com.itextpdf.layout.Document;
|
|
||||||
import com.itextpdf.layout.element.Image;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
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.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
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 io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
import org.apache.pdfbox.pdmodel.*;
|
|
||||||
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class ToSinglePageController {
|
public class ToSinglePageController {
|
||||||
|
|
||||||
|
@ -42,45 +35,52 @@ public class ToSinglePageController {
|
||||||
summary = "Convert a multi-page PDF into a single long page PDF",
|
summary = "Convert a multi-page PDF into a single long page PDF",
|
||||||
description = "This endpoint converts a multi-page PDF document into a single paged PDF document. The width of the single page will be same as the input's width, but the height will be the sum of all the pages' heights. Input:PDF Output:PDF Type:SISO"
|
description = "This endpoint converts a multi-page PDF document into a single paged PDF document. The width of the single page will be same as the input's width, but the height will be the sum of all the pages' heights. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> pdfToSinglePage(
|
public ResponseEntity<byte[]> pdfToSinglePage(@ModelAttribute PDFFile request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
|
||||||
@Parameter(description = "The input multi-page PDF file to be converted into a single page", required = true)
|
|
||||||
MultipartFile file) throws IOException {
|
|
||||||
|
|
||||||
PdfReader reader = new PdfReader(file.getInputStream());
|
// Load the source document
|
||||||
PdfDocument sourceDocument = new PdfDocument(reader);
|
PDDocument sourceDocument = PDDocument.load(request.getFileInput().getInputStream());
|
||||||
|
|
||||||
|
// Calculate total height and max width
|
||||||
float totalHeight = 0;
|
float totalHeight = 0;
|
||||||
float width = 0;
|
float maxWidth = 0;
|
||||||
|
for (PDPage page : sourceDocument.getPages()) {
|
||||||
for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) {
|
PDRectangle pageSize = page.getMediaBox();
|
||||||
Rectangle pageSize = sourceDocument.getPage(i).getPageSize();
|
|
||||||
totalHeight += pageSize.getHeight();
|
totalHeight += pageSize.getHeight();
|
||||||
if(width < pageSize.getWidth())
|
maxWidth = Math.max(maxWidth, pageSize.getWidth());
|
||||||
width = pageSize.getWidth();
|
}
|
||||||
|
|
||||||
|
// Create new document and page with calculated dimensions
|
||||||
|
PDDocument newDocument = new PDDocument();
|
||||||
|
PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight));
|
||||||
|
newDocument.addPage(newPage);
|
||||||
|
|
||||||
|
// Initialize the content stream of the new page
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);
|
||||||
|
contentStream.close();
|
||||||
|
|
||||||
|
LayerUtility layerUtility = new LayerUtility(newDocument);
|
||||||
|
float yOffset = totalHeight;
|
||||||
|
|
||||||
|
// For each page, copy its content to the new page at the correct offset
|
||||||
|
for (PDPage page : sourceDocument.getPages()) {
|
||||||
|
PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, sourceDocument.getPages().indexOf(page));
|
||||||
|
AffineTransform af = AffineTransform.getTranslateInstance(0, yOffset - page.getMediaBox().getHeight());
|
||||||
|
layerUtility.wrapInSaveRestore(newPage);
|
||||||
|
String defaultLayerName = "Layer" + sourceDocument.getPages().indexOf(page);
|
||||||
|
layerUtility.appendFormAsLayer(newPage, form, af, defaultLayerName);
|
||||||
|
yOffset -= page.getMediaBox().getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
PdfWriter writer = new PdfWriter(baos);
|
newDocument.save(baos);
|
||||||
PdfDocument newDocument = new PdfDocument(writer);
|
newDocument.close();
|
||||||
PageSize newPageSize = new PageSize(width, totalHeight);
|
|
||||||
newDocument.addNewPage(newPageSize);
|
|
||||||
|
|
||||||
Document layoutDoc = new Document(newDocument);
|
|
||||||
float yOffset = totalHeight;
|
|
||||||
|
|
||||||
for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) {
|
|
||||||
PdfFormXObject pageCopy = sourceDocument.getPage(i).copyAsFormXObject(newDocument);
|
|
||||||
Image copiedPage = new Image(pageCopy);
|
|
||||||
copiedPage.setFixedPosition(0, yOffset - sourceDocument.getPage(i).getPageSize().getHeight());
|
|
||||||
yOffset -= sourceDocument.getPage(i).getPageSize().getHeight();
|
|
||||||
layoutDoc.add(copiedPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutDoc.close();
|
|
||||||
sourceDocument.close();
|
sourceDocument.close();
|
||||||
|
|
||||||
byte[] result = baos.toByteArray();
|
byte[] result = baos.toByteArray();
|
||||||
return WebResponseUtils.bytesToWebResponse(result, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_singlePage.pdf");
|
return WebResponseUtils.bytesToWebResponse(result, request.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_singlePage.pdf");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||||
|
import org.springframework.web.servlet.view.RedirectView;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/api/v1/user")
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@PostMapping("/register")
|
||||||
|
public String register(@RequestParam String username, @RequestParam String password, Model model) {
|
||||||
|
if(userService.usernameExists(username)) {
|
||||||
|
model.addAttribute("error", "Username already exists");
|
||||||
|
return "register";
|
||||||
|
}
|
||||||
|
|
||||||
|
userService.saveUser(username, password);
|
||||||
|
return "redirect:/login?registered=true";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/change-username-and-password")
|
||||||
|
public RedirectView changeUsernameAndPassword(Principal principal,
|
||||||
|
@RequestParam String currentPassword,
|
||||||
|
@RequestParam String newUsername,
|
||||||
|
@RequestParam String newPassword,
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
RedirectAttributes redirectAttributes) {
|
||||||
|
if (principal == null) {
|
||||||
|
return new RedirectView("/change-creds?messageType=notAuthenticated");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<User> userOpt = userService.findByUsername(principal.getName());
|
||||||
|
|
||||||
|
if (userOpt == null || userOpt.isEmpty()) {
|
||||||
|
return new RedirectView("/change-creds?messageType=userNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userOpt.get();
|
||||||
|
|
||||||
|
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
||||||
|
return new RedirectView("/change-creds?messageType=incorrectPassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
|
||||||
|
return new RedirectView("/change-creds?messageType=usernameExists");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
userService.changePassword(user, newPassword);
|
||||||
|
if(newUsername != null && newUsername.length() > 0 && !user.getUsername().equals(newUsername)) {
|
||||||
|
userService.changeUsername(user, newUsername);
|
||||||
|
}
|
||||||
|
userService.changeFirstUse(user, false);
|
||||||
|
|
||||||
|
// Logout using Spring's utility
|
||||||
|
new SecurityContextLogoutHandler().logout(request, response, null);
|
||||||
|
|
||||||
|
return new RedirectView("/login?messageType=credsUpdated");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/change-username")
|
||||||
|
public RedirectView changeUsername(Principal principal,
|
||||||
|
@RequestParam String currentPassword,
|
||||||
|
@RequestParam String newUsername,
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
RedirectAttributes redirectAttributes) {
|
||||||
|
if (principal == null) {
|
||||||
|
return new RedirectView("/account?messageType=notAuthenticated");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<User> userOpt = userService.findByUsername(principal.getName());
|
||||||
|
|
||||||
|
if (userOpt == null || userOpt.isEmpty()) {
|
||||||
|
return new RedirectView("/account?messageType=userNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userOpt.get();
|
||||||
|
|
||||||
|
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
||||||
|
return new RedirectView("/account?messageType=incorrectPassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.getUsername().equals(newUsername) && userService.usernameExists(newUsername)) {
|
||||||
|
return new RedirectView("/account?messageType=usernameExists");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(newUsername != null && newUsername.length() > 0) {
|
||||||
|
userService.changeUsername(user, newUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout using Spring's utility
|
||||||
|
new SecurityContextLogoutHandler().logout(request, response, null);
|
||||||
|
|
||||||
|
return new RedirectView("/login?messageType=credsUpdated");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/change-password")
|
||||||
|
public RedirectView changePassword(Principal principal,
|
||||||
|
@RequestParam String currentPassword,
|
||||||
|
@RequestParam String newPassword,
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
RedirectAttributes redirectAttributes) {
|
||||||
|
if (principal == null) {
|
||||||
|
return new RedirectView("/account?messageType=notAuthenticated");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<User> userOpt = userService.findByUsername(principal.getName());
|
||||||
|
|
||||||
|
if (userOpt == null || userOpt.isEmpty()) {
|
||||||
|
return new RedirectView("/account?messageType=userNotFound");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userOpt.get();
|
||||||
|
|
||||||
|
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
||||||
|
return new RedirectView("/account?messageType=incorrectPassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
userService.changePassword(user, newPassword);
|
||||||
|
|
||||||
|
// Logout using Spring's utility
|
||||||
|
new SecurityContextLogoutHandler().logout(request, response, null);
|
||||||
|
|
||||||
|
return new RedirectView("/login?messageType=credsUpdated");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/updateUserSettings")
|
||||||
|
public String updateUserSettings(HttpServletRequest request, Principal principal) {
|
||||||
|
Map<String, String[]> paramMap = request.getParameterMap();
|
||||||
|
Map<String, String> updates = new HashMap<>();
|
||||||
|
|
||||||
|
System.out.println("Received parameter map: " + paramMap);
|
||||||
|
|
||||||
|
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||||
|
updates.put(entry.getKey(), entry.getValue()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Processed updates: " + updates);
|
||||||
|
|
||||||
|
// Assuming you have a method in userService to update the settings for a user
|
||||||
|
userService.updateUserSettings(principal.getName(), updates);
|
||||||
|
|
||||||
|
return "redirect:/account"; // Redirect to a page of your choice after updating
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PostMapping("/admin/saveUser")
|
||||||
|
public RedirectView saveUser(@RequestParam String username, @RequestParam String password, @RequestParam String role,
|
||||||
|
@RequestParam(name = "forceChange", required = false, defaultValue = "false") boolean forceChange) {
|
||||||
|
|
||||||
|
if(userService.usernameExists(username)) {
|
||||||
|
return new RedirectView("/addUsers?messageType=usernameExists");
|
||||||
|
}
|
||||||
|
userService.saveUser(username, password, role, forceChange);
|
||||||
|
return new RedirectView("/addUsers"); // Redirect to account page after adding the user
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PostMapping("/admin/deleteUser/{username}")
|
||||||
|
public String deleteUser(@PathVariable String username, Authentication authentication) {
|
||||||
|
|
||||||
|
// Get the currently authenticated username
|
||||||
|
String currentUsername = authentication.getName();
|
||||||
|
|
||||||
|
// Check if the provided username matches the current session's username
|
||||||
|
if (currentUsername.equals(username)) {
|
||||||
|
throw new IllegalArgumentException("Cannot delete currently logined in user.");
|
||||||
|
}
|
||||||
|
|
||||||
|
userService.deleteUser(username);
|
||||||
|
return "redirect:/addUsers";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/get-api-key")
|
||||||
|
public ResponseEntity<String> getApiKey(Principal principal) {
|
||||||
|
if (principal == null) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("User not authenticated.");
|
||||||
|
}
|
||||||
|
String username = principal.getName();
|
||||||
|
String apiKey = userService.getApiKeyForUser(username);
|
||||||
|
if (apiKey == null) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("API key not found for user.");
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/update-api-key")
|
||||||
|
public ResponseEntity<String> updateApiKey(Principal principal) {
|
||||||
|
if (principal == null) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("User not authenticated.");
|
||||||
|
}
|
||||||
|
String username = principal.getName();
|
||||||
|
User user = userService.refreshApiKeyForUser(username);
|
||||||
|
String apiKey = user.getApiKey();
|
||||||
|
if (apiKey == null) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("API key not found for user.");
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,36 +1,5 @@
|
||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
|
||||||
import org.commonmark.parser.Parser;
|
|
||||||
import org.commonmark.renderer.html.HtmlRenderer;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import stirling.software.SPDF.utils.FileToPdf;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
@ -41,7 +10,29 @@ import java.util.Map;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
|
||||||
|
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 org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
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
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertEpubToPdf {
|
public class ConvertEpubToPdf {
|
||||||
//TODO
|
//TODO
|
||||||
|
@ -52,9 +43,9 @@ public class ConvertEpubToPdf {
|
||||||
description = "This endpoint takes an EPUB file input and converts it to a single PDF."
|
description = "This endpoint takes an EPUB file input and converts it to a single PDF."
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> epubToSinglePdf(
|
public ResponseEntity<byte[]> epubToSinglePdf(
|
||||||
@RequestPart(required = true, value = "fileInput") MultipartFile fileInput)
|
@ModelAttribute GeneralFile request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
MultipartFile fileInput = request.getFileInput();
|
||||||
if (fileInput == null) {
|
if (fileInput == null) {
|
||||||
throw new IllegalArgumentException("Please provide an EPUB file for conversion.");
|
throw new IllegalArgumentException("Please provide an EPUB file for conversion.");
|
||||||
}
|
}
|
||||||
|
@ -83,7 +74,7 @@ public class ConvertEpubToPdf {
|
||||||
|
|
||||||
// Assuming a pseudo-code function that merges multiple PDFs into one.
|
// Assuming a pseudo-code function that merges multiple PDFs into one.
|
||||||
private byte[] mergeMultiplePdfsIntoOne(List<byte[]> individualPdfs) {
|
private byte[] mergeMultiplePdfsIntoOne(List<byte[]> individualPdfs) {
|
||||||
// You can use a library such as iText or PDFBox to perform the merging here.
|
// You can use a library such as PDFBox to perform the merging here.
|
||||||
// Return the byte[] of the merged PDF.
|
// Return the byte[] of the merged PDF.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,33 @@
|
||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.GeneralFile;
|
||||||
import stirling.software.SPDF.utils.FileToPdf;
|
import stirling.software.SPDF.utils.FileToPdf;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
|
||||||
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
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertHtmlToPDF {
|
public class ConvertHtmlToPDF {
|
||||||
|
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/html-to-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 = "This endpoint takes an HTML or ZIP file input and converts it to a PDF format."
|
description = "This endpoint takes an HTML or ZIP file input and converts it to a PDF format."
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> HtmlToPdf(
|
public ResponseEntity<byte[]> HtmlToPdf(
|
||||||
@RequestPart(required = true, value = "fileInput") MultipartFile fileInput) throws IOException, InterruptedException {
|
@ModelAttribute GeneralFile request)
|
||||||
|
throws Exception {
|
||||||
|
MultipartFile fileInput = request.getFileInput();
|
||||||
|
|
||||||
if (fileInput == null) {
|
if (fileInput == null) {
|
||||||
throw new IllegalArgumentException("Please provide an HTML or ZIP file for conversion.");
|
throw new IllegalArgumentException("Please provide an HTML or ZIP file for conversion.");
|
||||||
|
|
|
@ -11,43 +11,34 @@ import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
|
||||||
|
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertImgPDFController {
|
public class ConvertImgPDFController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-img")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/img")
|
||||||
@Operation(summary = "Convert PDF to image(s)",
|
@Operation(summary = "Convert PDF to image(s)",
|
||||||
description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image Type:SI-Conditional")
|
description = "This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image Type:SI-Conditional")
|
||||||
public ResponseEntity<Resource> convertToImage(
|
public ResponseEntity<Resource> convertToImage(@ModelAttribute ConvertToImageRequest request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile file = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file to be converted")
|
String imageFormat = request.getImageFormat();
|
||||||
MultipartFile file,
|
String singleOrMultiple = request.getSingleOrMultiple();
|
||||||
@RequestParam("imageFormat")
|
String colorType = request.getColorType();
|
||||||
@Parameter(description = "The output image format", schema = @Schema(allowableValues = {"png", "jpeg", "jpg", "gif"}))
|
String dpi = request.getDpi();
|
||||||
String imageFormat,
|
|
||||||
@RequestParam("singleOrMultiple")
|
|
||||||
@Parameter(description = "Choose between a single image containing all pages or separate images for each page", schema = @Schema(allowableValues = {"single", "multiple"}))
|
|
||||||
String singleOrMultiple,
|
|
||||||
@RequestParam("colorType")
|
|
||||||
@Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"color", "greyscale", "blackwhite"}))
|
|
||||||
String colorType,
|
|
||||||
@RequestParam("dpi")
|
|
||||||
@Parameter(description = "The DPI (dots per inch) for the output image(s)")
|
|
||||||
String dpi) throws IOException {
|
|
||||||
|
|
||||||
byte[] pdfBytes = file.getBytes();
|
byte[] pdfBytes = file.getBytes();
|
||||||
ImageType colorTypeResult = ImageType.RGB;
|
ImageType colorTypeResult = ImageType.RGB;
|
||||||
|
@ -83,24 +74,17 @@ public class ConvertImgPDFController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/img-to-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/img/pdf")
|
||||||
@Operation(summary = "Convert images to a PDF file",
|
@Operation(summary = "Convert images to a PDF file",
|
||||||
description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?")
|
description = "This endpoint converts one or more images to a PDF file. Users can specify whether to stretch the images to fit the PDF page, and whether to automatically rotate the images. Input:Image Output:PDF Type:SISO?")
|
||||||
public ResponseEntity<byte[]> convertToPdf(
|
public ResponseEntity<byte[]> convertToPdf(@ModelAttribute ConvertToPdfRequest request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile[] file = request.getFileInput();
|
||||||
@Parameter(description = "The input images to be converted to a PDF file")
|
String fitOption = request.getFitOption();
|
||||||
MultipartFile[] file,
|
String colorType = request.getColorType();
|
||||||
@RequestParam(defaultValue = "false", name = "stretchToFit")
|
boolean autoRotate = request.isAutoRotate();
|
||||||
@Parameter(description = "Whether to stretch the images to fit the PDF page or maintain the aspect ratio", example = "false")
|
|
||||||
boolean stretchToFit,
|
|
||||||
@RequestParam("colorType")
|
|
||||||
@Parameter(description = "The color type of the output image(s)", schema = @Schema(allowableValues = {"color", "greyscale", "blackwhite"}))
|
|
||||||
String colorType,
|
|
||||||
@RequestParam(defaultValue = "false", name = "autoRotate")
|
|
||||||
@Parameter(description = "Whether to automatically rotate the images to better fit the PDF page", example = "true")
|
|
||||||
boolean autoRotate) throws IOException {
|
|
||||||
// Convert the file to PDF and get the resulting bytes
|
// Convert the file to PDF and get the resulting bytes
|
||||||
byte[] bytes = PdfUtils.imageToPdf(file, stretchToFit, autoRotate, colorType);
|
byte[] bytes = PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType);
|
||||||
return WebResponseUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
|
return WebResponseUtils.bytesToWebResponse(bytes, file[0].getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_converted.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,35 @@
|
||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.parser.Parser;
|
import org.commonmark.parser.Parser;
|
||||||
import org.commonmark.renderer.html.HtmlRenderer;
|
import org.commonmark.renderer.html.HtmlRenderer;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.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;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertMarkdownToPdf {
|
public class ConvertMarkdownToPdf {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/markdown-to-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 = "This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format."
|
description = "This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format."
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> markdownToPdf(
|
public ResponseEntity<byte[]> markdownToPdf(
|
||||||
@RequestPart(required = true, value = "fileInput") MultipartFile fileInput)
|
@ModelAttribute GeneralFile request)
|
||||||
throws IOException, InterruptedException {
|
throws Exception {
|
||||||
|
MultipartFile fileInput = request.getFileInput();
|
||||||
|
|
||||||
if (fileInput == null) {
|
if (fileInput == null) {
|
||||||
throw new IllegalArgumentException("Please provide a Markdown file for conversion.");
|
throw new IllegalArgumentException("Please provide a Markdown file for conversion.");
|
||||||
|
|
|
@ -10,20 +10,22 @@ import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.GeneralFile;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertOfficeController {
|
public class ConvertOfficeController {
|
||||||
|
|
||||||
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
public byte[] convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||||
|
@ -58,19 +60,14 @@ public class ConvertOfficeController {
|
||||||
return fileExtension.matches(extensionPattern);
|
return fileExtension.matches(extensionPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/file-to-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/file/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a file to a PDF using LibreOffice",
|
summary = "Convert a file to a PDF using LibreOffice",
|
||||||
description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO"
|
description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> processFileToPDF(
|
public ResponseEntity<byte[]> processFileToPDF(@ModelAttribute GeneralFile request)
|
||||||
@RequestPart(required = true, value = "fileInput")
|
throws Exception {
|
||||||
@Parameter(
|
MultipartFile inputFile = request.getFileInput();
|
||||||
description = "The input file to be converted to a PDF file using LibreOffice",
|
|
||||||
required = true
|
|
||||||
)
|
|
||||||
MultipartFile inputFile
|
|
||||||
) throws IOException, InterruptedException {
|
|
||||||
// unused but can start server instance if startup time is to long
|
// unused but can start server instance if startup time is to long
|
||||||
// LibreOfficeListener.getInstance().start();
|
// LibreOfficeListener.getInstance().start();
|
||||||
|
|
||||||
|
|
|
@ -3,69 +3,67 @@ package stirling.software.SPDF.controller.api.converters;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
|
||||||
|
import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
|
||||||
|
import stirling.software.SPDF.model.api.converters.PdfToWordRequest;
|
||||||
import stirling.software.SPDF.utils.PDFToFile;
|
import stirling.software.SPDF.utils.PDFToFile;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertPDFToOffice {
|
public class ConvertPDFToOffice {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-html")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/html")
|
||||||
@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
|
@Operation(summary = "Convert PDF to HTML", description = "This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToHTML(
|
public ResponseEntity<byte[]> processPdfToHTML(@ModelAttribute PDFFile request)
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to HTML format", required = true) MultipartFile inputFile)
|
throws Exception {
|
||||||
throws IOException, InterruptedException {
|
MultipartFile inputFile = request.getFileInput();
|
||||||
PDFToFile pdfToFile = new PDFToFile();
|
PDFToFile pdfToFile = new PDFToFile();
|
||||||
return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import");
|
return pdfToFile.processPdfToOfficeFormat(inputFile, "html", "writer_pdf_import");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-presentation")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/presentation")
|
||||||
@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT Type:SISO")
|
@Operation(summary = "Convert PDF to Presentation format", description = "This endpoint converts a given PDF file to a Presentation format. Input:PDF Output:PPT Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToPresentation(
|
public ResponseEntity<byte[]> processPdfToPresentation(@ModelAttribute PdfToPresentationRequest request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@RequestParam("outputFormat") @Parameter(description = "The output Presentation format", schema = @Schema(allowableValues = {
|
String outputFormat = request.getOutputFormat();
|
||||||
"ppt", "pptx", "odp" })) String outputFormat)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
PDFToFile pdfToFile = new PDFToFile();
|
PDFToFile pdfToFile = new PDFToFile();
|
||||||
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import");
|
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-text")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/text")
|
||||||
@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT Type:SISO")
|
@Operation(summary = "Convert PDF to Text or RTF format", description = "This endpoint converts a given PDF file to Text or RTF format. Input:PDF Output:TXT Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToRTForTXT(
|
public ResponseEntity<byte[]> processPdfToRTForTXT(@ModelAttribute PdfToTextOrRTFRequest request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@RequestParam("outputFormat") @Parameter(description = "The output Text or RTF format", schema = @Schema(allowableValues = {
|
String outputFormat = request.getOutputFormat();
|
||||||
"rtf", "txt:Text" })) String outputFormat)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
PDFToFile pdfToFile = new PDFToFile();
|
PDFToFile pdfToFile = new PDFToFile();
|
||||||
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
|
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-word")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/word")
|
||||||
@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD Type:SISO")
|
@Operation(summary = "Convert PDF to Word document", description = "This endpoint converts a given PDF file to a Word document format. Input:PDF Output:WORD Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToWord(
|
public ResponseEntity<byte[]> processPdfToWord(@ModelAttribute PdfToWordRequest request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file") MultipartFile inputFile,
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@RequestParam("outputFormat") @Parameter(description = "The output Word document format", schema = @Schema(allowableValues = {
|
String outputFormat = request.getOutputFormat();
|
||||||
"doc", "docx", "odt" })) String outputFormat)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
PDFToFile pdfToFile = new PDFToFile();
|
PDFToFile pdfToFile = new PDFToFile();
|
||||||
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
|
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-xml")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/xml")
|
||||||
@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO")
|
@Operation(summary = "Convert PDF to XML", description = "This endpoint converts a PDF file to an XML file. Input:PDF Output:XML Type:SISO")
|
||||||
public ResponseEntity<byte[]> processPdfToXML(
|
public ResponseEntity<byte[]> processPdfToXML(@ModelAttribute PDFFile request)
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to an XML file", required = true) MultipartFile inputFile)
|
throws Exception {
|
||||||
throws IOException, InterruptedException {
|
MultipartFile inputFile = request.getFileInput();
|
||||||
|
|
||||||
PDFToFile pdfToFile = new PDFToFile();
|
PDFToFile pdfToFile = new PDFToFile();
|
||||||
return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import");
|
return pdfToFile.processPdfToOfficeFormat(inputFile, "xml", "writer_pdf_import");
|
||||||
|
|
|
@ -1,37 +1,37 @@
|
||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertPDFToPDFA {
|
public class ConvertPDFToPDFA {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-pdfa")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a PDF to a PDF/A",
|
summary = "Convert a PDF to a PDF/A",
|
||||||
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO"
|
description = "This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> pdfToPdfA(
|
public ResponseEntity<byte[]> pdfToPdfA(@ModelAttribute PDFFile request)
|
||||||
@RequestPart(required = true, value = "fileInput")
|
throws Exception {
|
||||||
@Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true)
|
MultipartFile inputFile = request.getFileInput();
|
||||||
MultipartFile inputFile) throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
|
|
|
@ -7,15 +7,14 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
|
@ -23,17 +22,16 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertWebsiteToPDF {
|
public class ConvertWebsiteToPDF {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/url-to-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",
|
||||||
description = "This endpoint fetches content from a URL and converts it to a PDF format."
|
description = "This endpoint fetches content from a URL and converts it to a PDF format."
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> urlToPdf(
|
public ResponseEntity<byte[]> urlToPdf(@ModelAttribute UrlToPdfRequest request) throws IOException, InterruptedException {
|
||||||
@RequestParam(required = true, value = "urlInput")
|
String URL = request.getUrlInput();
|
||||||
@Parameter(description = "The input URL to be converted to a PDF file", required = true)
|
|
||||||
String URL) throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
// Validate the URL format
|
// Validate the URL format
|
||||||
if(!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
|
if(!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
|
||||||
|
|
|
@ -1,39 +1,40 @@
|
||||||
package stirling.software.SPDF.controller.api.filters;
|
package stirling.software.SPDF.controller.api.filters;
|
||||||
|
|
||||||
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.List;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.PDFComparisonAndCount;
|
||||||
|
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||||
|
import stirling.software.SPDF.model.api.filter.ContainsTextRequest;
|
||||||
|
import stirling.software.SPDF.model.api.filter.FileSizeRequest;
|
||||||
|
import stirling.software.SPDF.model.api.filter.PageRotationRequest;
|
||||||
|
import stirling.software.SPDF.model.api.filter.PageSizeRequest;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/filter")
|
||||||
@Tag(name = "Filter", description = "Filter APIs")
|
@Tag(name = "Filter", description = "Filter APIs")
|
||||||
public class FilterController {
|
public class FilterController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
|
||||||
@Operation(summary = "Checks if a PDF contains set text, returns true if does", description = "Input:PDF Output:Boolean Type:SISO")
|
@Operation(summary = "Checks if a PDF contains set text, returns true if does", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> containsText(
|
public ResponseEntity<byte[]> containsText(@ModelAttribute ContainsTextRequest request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) MultipartFile inputFile,
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "The text to check for", required = true) String text,
|
String text = request.getText();
|
||||||
@Parameter(description = "The page number to check for text on accepts 'All', ranges like '1-4'", required = false) String pageNumber)
|
String pageNumber = request.getPageNumbers();
|
||||||
throws IOException, InterruptedException {
|
|
||||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
||||||
if (PdfUtils.hasText(pdfDocument, pageNumber, text))
|
if (PdfUtils.hasText(pdfDocument, pageNumber, text))
|
||||||
return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename());
|
return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename());
|
||||||
|
@ -43,10 +44,11 @@ public class FilterController {
|
||||||
// TODO
|
// TODO
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
|
||||||
@Operation(summary = "Checks if a PDF contains an image", description = "Input:PDF Output:Boolean Type:SISO")
|
@Operation(summary = "Checks if a PDF contains an image", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> containsImage(
|
public ResponseEntity<byte[]> containsImage(@ModelAttribute PDFWithPageNums request)
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to be converted to a PDF/A file", required = true) MultipartFile inputFile,
|
|
||||||
@Parameter(description = "The page number to check for image on accepts 'All', ranges like '1-4'", required = false) String pageNumber)
|
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
MultipartFile inputFile = request.getFileInput();
|
||||||
|
String pageNumber = request.getPageNumbers();
|
||||||
|
|
||||||
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
PDDocument pdfDocument = PDDocument.load(inputFile.getInputStream());
|
||||||
if (PdfUtils.hasImages(pdfDocument, pageNumber))
|
if (PdfUtils.hasImages(pdfDocument, pageNumber))
|
||||||
return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename());
|
return WebResponseUtils.pdfDocToWebResponse(pdfDocument, inputFile.getOriginalFilename());
|
||||||
|
@ -55,12 +57,10 @@ public class FilterController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
|
||||||
@Operation(summary = "Checks if a PDF is greater, less or equal to a setPageCount", description = "Input:PDF Output:Boolean Type:SISO")
|
@Operation(summary = "Checks if a PDF is greater, less or equal to a setPageCount", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> pageCount(
|
public ResponseEntity<byte[]> pageCount(@ModelAttribute PDFComparisonAndCount request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "Page Count", required = true) String pageCount,
|
String pageCount = request.getPageCount();
|
||||||
@Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = {
|
String comparator = request.getComparator();
|
||||||
"Greater", "Equal", "Less" })) String comparator)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
// Load the PDF
|
// Load the PDF
|
||||||
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
||||||
int actualPageCount = document.getNumberOfPages();
|
int actualPageCount = document.getNumberOfPages();
|
||||||
|
@ -88,12 +88,10 @@ public class FilterController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
|
||||||
@Operation(summary = "Checks if a PDF is of a certain size", description = "Input:PDF Output:Boolean Type:SISO")
|
@Operation(summary = "Checks if a PDF is of a certain size", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> pageSize(
|
public ResponseEntity<byte[]> pageSize(@ModelAttribute PageSizeRequest request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "Standard Page Size", required = true) String standardPageSize,
|
String standardPageSize = request.getStandardPageSize();
|
||||||
@Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = {
|
String comparator = request.getComparator();
|
||||||
"Greater", "Equal", "Less" })) String comparator)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
// Load the PDF
|
// Load the PDF
|
||||||
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
||||||
|
@ -131,12 +129,10 @@ public class FilterController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
|
||||||
@Operation(summary = "Checks if a PDF is a set file size", description = "Input:PDF Output:Boolean Type:SISO")
|
@Operation(summary = "Checks if a PDF is a set file size", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> fileSize(
|
public ResponseEntity<byte[]> fileSize(@ModelAttribute FileSizeRequest request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "File Size", required = true) String fileSize,
|
String fileSize = request.getFileSize();
|
||||||
@Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = {
|
String comparator = request.getComparator();
|
||||||
"Greater", "Equal", "Less" })) String comparator)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
// Get the file size
|
// Get the file size
|
||||||
long actualFileSize = inputFile.getSize();
|
long actualFileSize = inputFile.getSize();
|
||||||
|
@ -164,12 +160,10 @@ public class FilterController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
|
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
|
||||||
@Operation(summary = "Checks if a PDF is of a certain rotation", description = "Input:PDF Output:Boolean Type:SISO")
|
@Operation(summary = "Checks if a PDF is of a certain rotation", description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
public ResponseEntity<byte[]> pageRotation(
|
public ResponseEntity<byte[]> pageRotation(@ModelAttribute PageRotationRequest request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "Rotation in degrees", required = true) int rotation,
|
int rotation = request.getRotation();
|
||||||
@Parameter(description = "Comparison type", schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than", allowableValues = {
|
String comparator = request.getComparator();
|
||||||
"Greater", "Equal", "Less" })) String comparator)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
// Load the PDF
|
// Load the PDF
|
||||||
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
PDDocument document = PDDocument.load(inputFile.getInputStream());
|
||||||
|
|
|
@ -1,77 +1,29 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
|
import org.apache.pdfbox.text.TextPosition;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
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.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
import org.apache.pdfbox.pdmodel.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream.*;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.http.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import io.swagger.v3.oas.annotations.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.*;
|
|
||||||
import io.swagger.v3.oas.annotations.parameters.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
|
||||||
import org.apache.pdfbox.text.TextPosition;
|
|
||||||
import org.apache.tomcat.util.http.ResponseUtil;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import com.itextpdf.io.font.constants.StandardFonts;
|
|
||||||
import com.itextpdf.kernel.font.PdfFont;
|
|
||||||
import com.itextpdf.kernel.font.PdfFontFactory;
|
|
||||||
import com.itextpdf.kernel.geom.Rectangle;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfReader;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfPage;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
|
||||||
import com.itextpdf.layout.Canvas;
|
|
||||||
import com.itextpdf.layout.element.Paragraph;
|
|
||||||
import com.itextpdf.layout.properties.TextAlignment;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.*;
|
|
||||||
import org.apache.pdfbox.text.*;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import io.swagger.v3.oas.annotations.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class AutoRenameController {
|
public class AutoRenameController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AutoRenameController.class);
|
private static final Logger logger = LoggerFactory.getLogger(AutoRenameController.class);
|
||||||
|
@ -81,10 +33,9 @@ public class AutoRenameController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/auto-rename")
|
@PostMapping(consumes = "multipart/form-data", value = "/auto-rename")
|
||||||
@Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO")
|
@Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> extractHeader(
|
public ResponseEntity<byte[]> extractHeader(@ModelAttribute ExtractHeaderRequest request) throws Exception {
|
||||||
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file from which the header is to be extracted.", required = true) MultipartFile file,
|
MultipartFile file = request.getFileInput();
|
||||||
@RequestParam(required = false, defaultValue = "false") @Parameter(description = "Flag indicating whether to use the first text as a fallback if no suitable title is found. Defaults to false.", required = false) Boolean useFirstTextAsFallback)
|
Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(file.getInputStream());
|
PDDocument document = PDDocument.load(file.getInputStream());
|
||||||
PDFTextStripper reader = new PDFTextStripper() {
|
PDFTextStripper reader = new PDFTextStripper() {
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBufferByte;
|
import java.awt.image.DataBufferByte;
|
||||||
import java.awt.image.DataBufferInt;
|
import java.awt.image.DataBufferInt;
|
||||||
|
@ -16,8 +16,9 @@ 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;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
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;
|
||||||
|
|
||||||
|
@ -29,21 +30,23 @@ 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 stirling.software.SPDF.utils.WebResponseUtils;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class AutoSplitPdfController {
|
public class AutoSplitPdfController {
|
||||||
|
|
||||||
private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
|
private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
|
||||||
|
|
||||||
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
||||||
@Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP Type:SISO")
|
@Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP Type:SISO")
|
||||||
public ResponseEntity<byte[]> autoSplitPdf(
|
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request) throws IOException {
|
||||||
@RequestParam("fileInput") @Parameter(description = "The input PDF file which needs to be split into separate documents based on QR code boundaries.", required = true) MultipartFile file,
|
MultipartFile file = request.getFileInput();
|
||||||
@RequestParam(value ="duplexMode",defaultValue = "false") @Parameter(description = "Flag indicating if the duplex mode is active, where the page after the divider also gets removed.", required = false) boolean duplexMode)
|
boolean duplexMode = request.isDuplexMode();
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
InputStream inputStream = file.getInputStream();
|
InputStream inputStream = file.getInputStream();
|
||||||
PDDocument document = PDDocument.load(inputStream);
|
PDDocument document = PDDocument.load(inputStream);
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
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;
|
||||||
|
@ -20,22 +20,23 @@ import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
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.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
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.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class BlankPageController {
|
public class BlankPageController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
||||||
|
@ -43,16 +44,10 @@ public class BlankPageController {
|
||||||
summary = "Remove blank pages from a PDF file",
|
summary = "Remove blank pages from a PDF file",
|
||||||
description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF Type:SISO"
|
description = "This endpoint removes blank pages from a given PDF file. Users can specify the threshold and white percentage to tune the detection of blank pages. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> removeBlankPages(
|
public ResponseEntity<byte[]> removeBlankPages(@ModelAttribute RemoveBlankPagesRequest request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file from which blank pages will be removed", required = true)
|
int threshold = request.getThreshold();
|
||||||
MultipartFile inputFile,
|
float whitePercent = request.getWhitePercent();
|
||||||
@RequestParam(defaultValue = "10", name = "threshold")
|
|
||||||
@Parameter(description = "The threshold value to determine blank pages", example = "10")
|
|
||||||
int threshold,
|
|
||||||
@RequestParam(defaultValue = "99.9", name = "whitePercent")
|
|
||||||
@Parameter(description = "The percentage of white color on a page to consider it as blank", example = "99.9")
|
|
||||||
float whitePercent) throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
PDDocument document = null;
|
PDDocument document = null;
|
||||||
try {
|
try {
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
@ -22,35 +22,34 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class CompressController {
|
public class CompressController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
||||||
@Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO")
|
@Operation(summary = "Optimize PDF file", description = "This endpoint accepts a PDF file and optimizes it based on the provided parameters. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> optimizePdf(
|
public ResponseEntity<byte[]> optimizePdf(@ModelAttribute OptimizePdfRequest request) throws Exception {
|
||||||
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file to be optimized.", required = true) MultipartFile inputFile,
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@RequestParam(required = false, value = "optimizeLevel") @Parameter(description = "The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.", schema = @Schema(allowableValues = {
|
Integer optimizeLevel = request.getOptimizeLevel();
|
||||||
"1", "2", "3", "4", "5" })) Integer optimizeLevel,
|
String expectedOutputSizeString = request.getExpectedOutputSize();
|
||||||
@RequestParam(value = "expectedOutputSize", required = false) @Parameter(description = "The expected output size, e.g. '100MB', '25KB', etc.", required = false) String expectedOutputSizeString)
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
if(expectedOutputSizeString == null && optimizeLevel == null) {
|
if(expectedOutputSizeString == null && optimizeLevel == null) {
|
||||||
throw new Exception("Both expected output size and optimize level are not specified");
|
throw new Exception("Both expected output size and optimize level are not specified");
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
@ -24,20 +24,21 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class ExtractImageScansController {
|
public class ExtractImageScansController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class);
|
||||||
|
@ -46,26 +47,16 @@ public class ExtractImageScansController {
|
||||||
@Operation(summary = "Extract image scans from an input file",
|
@Operation(summary = "Extract image scans from an input file",
|
||||||
description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
description = "This endpoint extracts image scans from a given file based on certain parameters. Users can specify angle threshold, tolerance, minimum area, minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
||||||
public ResponseEntity<byte[]> extractImageScans(
|
public ResponseEntity<byte[]> extractImageScans(
|
||||||
@RequestPart(required = true, value = "fileInput")
|
@RequestBody(
|
||||||
@Parameter(description = "The input file containing image scans")
|
description = "Form data containing file and extraction parameters",
|
||||||
MultipartFile inputFile,
|
required = true,
|
||||||
@RequestParam(name = "angle_threshold", defaultValue = "5")
|
content = @Content(
|
||||||
@Parameter(description = "The angle threshold for the image scan extraction", example = "5")
|
mediaType = "multipart/form-data",
|
||||||
int angleThreshold,
|
schema = @Schema(implementation = ExtractImageScansRequest.class) // This should represent your form's structure
|
||||||
@RequestParam(name = "tolerance", defaultValue = "20")
|
)
|
||||||
@Parameter(description = "The tolerance for the image scan extraction", example = "20")
|
)
|
||||||
int tolerance,
|
ExtractImageScansRequest form) throws IOException, InterruptedException {
|
||||||
@RequestParam(name = "min_area", defaultValue = "8000")
|
String fileName = form.getFileInput().getOriginalFilename();
|
||||||
@Parameter(description = "The minimum area for the image scan extraction", example = "8000")
|
|
||||||
int minArea,
|
|
||||||
@RequestParam(name = "min_contour_area", defaultValue = "500")
|
|
||||||
@Parameter(description = "The minimum contour area for the image scan extraction", example = "500")
|
|
||||||
int minContourArea,
|
|
||||||
@RequestParam(name = "border_size", defaultValue = "1")
|
|
||||||
@Parameter(description = "The border size for the image scan extraction", example = "1")
|
|
||||||
int borderSize) throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
String fileName = inputFile.getOriginalFilename();
|
|
||||||
String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
|
String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
|
||||||
|
|
||||||
List<String> images = new ArrayList<>();
|
List<String> images = new ArrayList<>();
|
||||||
|
@ -73,7 +64,7 @@ public class ExtractImageScansController {
|
||||||
// Check if input file is a PDF
|
// Check if input file is a PDF
|
||||||
if (extension.equalsIgnoreCase("pdf")) {
|
if (extension.equalsIgnoreCase("pdf")) {
|
||||||
// Load PDF document
|
// Load PDF document
|
||||||
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(inputFile.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<>();
|
||||||
|
@ -93,7 +84,7 @@ public class ExtractImageScansController {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Path tempInputFile = Files.createTempFile("input_", "." + extension);
|
Path tempInputFile = Files.createTempFile("input_", "." + extension);
|
||||||
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(form.getFileInput().getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
// Add input file path to images list
|
// Add input file path to images list
|
||||||
images.add(tempInputFile.toString());
|
images.add(tempInputFile.toString());
|
||||||
}
|
}
|
||||||
|
@ -109,11 +100,11 @@ public class ExtractImageScansController {
|
||||||
"./scripts/split_photos.py",
|
"./scripts/split_photos.py",
|
||||||
images.get(i),
|
images.get(i),
|
||||||
tempDir.toString(),
|
tempDir.toString(),
|
||||||
"--angle_threshold", String.valueOf(angleThreshold),
|
"--angle_threshold", String.valueOf(form.getAngleThreshold()),
|
||||||
"--tolerance", String.valueOf(tolerance),
|
"--tolerance", String.valueOf(form.getTolerance()),
|
||||||
"--min_area", String.valueOf(minArea),
|
"--min_area", String.valueOf(form.getMinArea()),
|
||||||
"--min_contour_area", String.valueOf(minContourArea),
|
"--min_contour_area", String.valueOf(form.getMinContourArea()),
|
||||||
"--border_size", String.valueOf(borderSize)
|
"--border_size", String.valueOf(form.getBorderSize())
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
@ -20,19 +23,19 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.PDFWithImageFormatRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class ExtractImagesController {
|
public class ExtractImagesController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
|
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
|
||||||
|
@ -40,13 +43,9 @@ public class ExtractImagesController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
||||||
@Operation(summary = "Extract images from a PDF file",
|
@Operation(summary = "Extract images from a PDF file",
|
||||||
description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
description = "This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO")
|
||||||
public ResponseEntity<byte[]> extractImages(
|
public ResponseEntity<byte[]> extractImages(@ModelAttribute PDFWithImageFormatRequest request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile file = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file containing images")
|
String format = request.getFormat();
|
||||||
MultipartFile file,
|
|
||||||
@RequestParam("format")
|
|
||||||
@Parameter(description = "The output image format e.g., 'png', 'jpeg', or 'gif'", schema = @Schema(allowableValues = {"png", "jpeg", "gif"}))
|
|
||||||
String format) throws IOException {
|
|
||||||
|
|
||||||
System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
|
System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
|
||||||
PDDocument document = PDDocument.load(file.getBytes());
|
PDDocument document = PDDocument.load(file.getBytes());
|
||||||
|
@ -62,7 +61,8 @@ public class ExtractImagesController {
|
||||||
|
|
||||||
int imageIndex = 1;
|
int imageIndex = 1;
|
||||||
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
|
String filename = file.getOriginalFilename().replaceFirst("[.][^.]+$", "");
|
||||||
int pageNum = 1;
|
int pageNum = 0;
|
||||||
|
Set<Integer> processedImages = new HashSet<>();
|
||||||
// Iterate over each page
|
// Iterate over each page
|
||||||
for (PDPage page : document.getPages()) {
|
for (PDPage page : document.getPages()) {
|
||||||
++pageNum;
|
++pageNum;
|
||||||
|
@ -70,6 +70,11 @@ public class ExtractImagesController {
|
||||||
for (COSName name : page.getResources().getXObjectNames()) {
|
for (COSName name : page.getResources().getXObjectNames()) {
|
||||||
if (page.getResources().isImageXObject(name)) {
|
if (page.getResources().isImageXObject(name)) {
|
||||||
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
|
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
|
||||||
|
int imageHash = image.hashCode();
|
||||||
|
if(processedImages.contains(imageHash)) {
|
||||||
|
continue; // Skip already processed images
|
||||||
|
}
|
||||||
|
processedImages.add(imageHash);
|
||||||
|
|
||||||
// Convert image to desired format
|
// Convert image to desired format
|
||||||
RenderedImage renderedImage = image.getImage();
|
RenderedImage renderedImage = image.getImage();
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
|
@ -9,6 +9,7 @@ import java.awt.image.BufferedImageOp;
|
||||||
import java.awt.image.ConvolveOp;
|
import java.awt.image.ConvolveOp;
|
||||||
import java.awt.image.Kernel;
|
import java.awt.image.Kernel;
|
||||||
import java.awt.image.RescaleOp;
|
import java.awt.image.RescaleOp;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
//Required for file input/output
|
//Required for file input/output
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -29,21 +30,21 @@ import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
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 com.itextpdf.io.source.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
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.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class FakeScanControllerWIP {
|
public class FakeScanControllerWIP {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
|
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
|
||||||
|
@ -55,10 +56,8 @@ public class FakeScanControllerWIP {
|
||||||
summary = "Repair a PDF file",
|
summary = "Repair a PDF file",
|
||||||
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response."
|
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response."
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> repairPdf(
|
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file to be repaired", required = true)
|
|
||||||
MultipartFile inputFile) throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(inputFile.getBytes());
|
PDDocument document = PDDocument.load(inputFile.getBytes());
|
||||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
@ -11,19 +11,20 @@ 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;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.misc.MetadataRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class MetadataController {
|
public class MetadataController {
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,44 +42,28 @@ public class MetadataController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
||||||
@Operation(summary = "Update metadata of a PDF file",
|
@Operation(summary = "Update metadata of a PDF file",
|
||||||
description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF Type:SISO")
|
description = "This endpoint allows you to update the metadata of a given PDF file. You can add, modify, or delete standard and custom metadata fields. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> metadata(
|
public ResponseEntity<byte[]> metadata(@ModelAttribute MetadataRequest request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
|
||||||
@Parameter(description = "The input PDF file to update metadata")
|
|
||||||
MultipartFile pdfFile,
|
|
||||||
@RequestParam(value = "deleteAll", required = false, defaultValue = "false")
|
|
||||||
@Parameter(description = "Delete all metadata if set to true")
|
|
||||||
Boolean deleteAll,
|
|
||||||
@RequestParam(value = "author", required = false)
|
|
||||||
@Parameter(description = "The author of the document")
|
|
||||||
String author,
|
|
||||||
@RequestParam(value = "creationDate", required = false)
|
|
||||||
@Parameter(description = "The creation date of the document (format: yyyy/MM/dd HH:mm:ss)")
|
|
||||||
String creationDate,
|
|
||||||
@RequestParam(value = "creator", required = false)
|
|
||||||
@Parameter(description = "The creator of the document")
|
|
||||||
String creator,
|
|
||||||
@RequestParam(value = "keywords", required = false)
|
|
||||||
@Parameter(description = "The keywords for the document")
|
|
||||||
String keywords,
|
|
||||||
@RequestParam(value = "modificationDate", required = false)
|
|
||||||
@Parameter(description = "The modification date of the document (format: yyyy/MM/dd HH:mm:ss)")
|
|
||||||
String modificationDate,
|
|
||||||
@RequestParam(value = "producer", required = false)
|
|
||||||
@Parameter(description = "The producer of the document")
|
|
||||||
String producer,
|
|
||||||
@RequestParam(value = "subject", required = false)
|
|
||||||
@Parameter(description = "The subject of the document")
|
|
||||||
String subject,
|
|
||||||
@RequestParam(value = "title", required = false)
|
|
||||||
@Parameter(description = "The title of the document")
|
|
||||||
String title,
|
|
||||||
@RequestParam(value = "trapped", required = false)
|
|
||||||
@Parameter(description = "The trapped status of the document")
|
|
||||||
String trapped,
|
|
||||||
@Parameter(description = "Map list of key and value of custom parameters, note these must start with customKey and customValue if they are non standard")
|
|
||||||
@RequestParam Map<String, String> allRequestParams)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
|
// Extract PDF file from the request object
|
||||||
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
|
|
||||||
|
// Extract metadata information
|
||||||
|
Boolean deleteAll = request.isDeleteAll();
|
||||||
|
String author = request.getAuthor();
|
||||||
|
String creationDate = request.getCreationDate();
|
||||||
|
String creator = request.getCreator();
|
||||||
|
String keywords = request.getKeywords();
|
||||||
|
String modificationDate = request.getModificationDate();
|
||||||
|
String producer = request.getProducer();
|
||||||
|
String subject = request.getSubject();
|
||||||
|
String title = request.getTitle();
|
||||||
|
String trapped = request.getTrapped();
|
||||||
|
|
||||||
|
// Extract additional custom parameters
|
||||||
|
Map<String, String> allRequestParams = request.getAllRequestParams();
|
||||||
|
if(allRequestParams == null) {
|
||||||
|
allRequestParams = new java.util.HashMap<String, String>();
|
||||||
|
}
|
||||||
// Load the PDF file into a PDDocument
|
// Load the PDF file into a PDDocument
|
||||||
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
PDDocument document = PDDocument.load(pdfFile.getBytes());
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -18,22 +18,22 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class OCRController {
|
public class OCRController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
|
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
|
||||||
|
@ -51,35 +51,16 @@ public class OCRController {
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
||||||
@Operation(summary = "Process a PDF file with OCR",
|
@Operation(summary = "Process a PDF file with OCR",
|
||||||
description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional")
|
description = "This endpoint processes a PDF file using OCR (Optical Character Recognition). Users can specify languages, sidecar, deskew, clean, cleanFinal, ocrType, ocrRenderType, and removeImagesAfter options. Input:PDF Output:PDF Type:SI-Conditional")
|
||||||
public ResponseEntity<byte[]> processPdfWithOCR(
|
public ResponseEntity<byte[]> processPdfWithOCR(@ModelAttribute ProcessPdfWithOcrRequest request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file to be processed with OCR")
|
List<String> selectedLanguages = request.getLanguages();
|
||||||
MultipartFile inputFile,
|
Boolean sidecar = request.isSidecar();
|
||||||
@RequestParam("languages")
|
Boolean deskew = request.isDeskew();
|
||||||
@Parameter(description = "List of languages to use in OCR processing")
|
Boolean clean = request.isClean();
|
||||||
List<String> selectedLanguages,
|
Boolean cleanFinal = request.isCleanFinal();
|
||||||
@RequestParam(name = "sidecar", required = false)
|
String ocrType = request.getOcrType();
|
||||||
@Parameter(description = "Include OCR text in a sidecar text file if set to true")
|
String ocrRenderType = request.getOcrRenderType();
|
||||||
Boolean sidecar,
|
Boolean removeImagesAfter = request.isRemoveImagesAfter();
|
||||||
@RequestParam(name = "deskew", required = false)
|
|
||||||
@Parameter(description = "Deskew the input file if set to true")
|
|
||||||
Boolean deskew,
|
|
||||||
@RequestParam(name = "clean", required = false)
|
|
||||||
@Parameter(description = "Clean the input file if set to true")
|
|
||||||
Boolean clean,
|
|
||||||
@RequestParam(name = "clean-final", required = false)
|
|
||||||
@Parameter(description = "Clean the final output if set to true")
|
|
||||||
Boolean cleanFinal,
|
|
||||||
@RequestParam(name = "ocrType", required = false)
|
|
||||||
@Parameter(description = "Specify the OCR type, e.g., 'skip-text', 'force-ocr', or 'Normal'", schema = @Schema(allowableValues = {"skip-text", "force-ocr", "Normal"}))
|
|
||||||
String ocrType,
|
|
||||||
@RequestParam(name = "ocrRenderType", required = false, defaultValue = "hocr")
|
|
||||||
@Parameter(description = "Specify the OCR render type, either 'hocr' or 'sandwich'", schema = @Schema(allowableValues = {"hocr", "sandwich"}))
|
|
||||||
String ocrRenderType,
|
|
||||||
@RequestParam(name = "removeImagesAfter", required = false)
|
|
||||||
@Parameter(description = "Remove images from the output PDF if set to true")
|
|
||||||
Boolean removeImagesAfter) throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
// --output-type pdfa
|
// --output-type pdfa
|
||||||
if (selectedLanguages == null || selectedLanguages.isEmpty()) {
|
if (selectedLanguages == null || selectedLanguages.isEmpty()) {
|
||||||
throw new IOException("Please select at least one language.");
|
throw new IOException("Please select at least one language.");
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -6,20 +6,21 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
import stirling.software.SPDF.utils.PdfUtils;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class OverlayImageController {
|
public class OverlayImageController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
|
||||||
|
@ -29,22 +30,12 @@ public class OverlayImageController {
|
||||||
summary = "Overlay image onto a PDF file",
|
summary = "Overlay image onto a PDF file",
|
||||||
description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO"
|
description = "This endpoint overlays an image onto a PDF file at the specified coordinates. The image can be overlaid on every page of the PDF if specified. Input:PDF/IMAGE Output:PDF Type:MF-SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> overlayImage(
|
public ResponseEntity<byte[]> overlayImage(@ModelAttribute OverlayImageRequest request) {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file to overlay the image onto.", required = true)
|
MultipartFile imageFile = request.getImageFile();
|
||||||
MultipartFile pdfFile,
|
float x = request.getX();
|
||||||
@RequestParam("fileInput2")
|
float y = request.getY();
|
||||||
@Parameter(description = "The image file to be overlaid onto the PDF.", required = true)
|
boolean everyPage = request.isEveryPage();
|
||||||
MultipartFile imageFile,
|
|
||||||
@RequestParam("x")
|
|
||||||
@Parameter(description = "The x-coordinate at which to place the top-left corner of the image.", example = "0")
|
|
||||||
float x,
|
|
||||||
@RequestParam("y")
|
|
||||||
@Parameter(description = "The y-coordinate at which to place the top-left corner of the image.", example = "0")
|
|
||||||
float y,
|
|
||||||
@RequestParam("everyPage")
|
|
||||||
@Parameter(description = "Whether to overlay the image onto every page of the PDF.", example = "false")
|
|
||||||
boolean everyPage) {
|
|
||||||
try {
|
try {
|
||||||
byte[] pdfBytes = pdfFile.getBytes();
|
byte[] pdfBytes = pdfFile.getBytes();
|
||||||
byte[] imageBytes = imageFile.getBytes();
|
byte[] imageBytes = imageFile.getBytes();
|
|
@ -0,0 +1,135 @@
|
||||||
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.PDType1Font;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
|
public class PageNumbersController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
||||||
|
|
||||||
|
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
||||||
|
@Operation(summary = "Add page numbers to a PDF document", description = "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> addPageNumbers(@ModelAttribute AddPageNumbersRequest request) throws IOException {
|
||||||
|
MultipartFile file = request.getFileInput();
|
||||||
|
String customMargin = request.getCustomMargin();
|
||||||
|
int position = request.getPosition();
|
||||||
|
int startingNumber = request.getStartingNumber();
|
||||||
|
String pagesToNumber = request.getPagesToNumber();
|
||||||
|
String customText = request.getCustomText();
|
||||||
|
int pageNumber = startingNumber;
|
||||||
|
byte[] fileBytes = file.getBytes();
|
||||||
|
PDDocument document = PDDocument.load(fileBytes);
|
||||||
|
|
||||||
|
float marginFactor;
|
||||||
|
switch (customMargin.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
float fontSize = 12.0f;
|
||||||
|
PDType1Font font = PDType1Font.HELVETICA;
|
||||||
|
if(pagesToNumber == null || pagesToNumber.length() == 0) {
|
||||||
|
pagesToNumber = "all";
|
||||||
|
}
|
||||||
|
if(customText == null || customText.length() == 0) {
|
||||||
|
customText = "{n}";
|
||||||
|
}
|
||||||
|
List<Integer> pagesToNumberList = GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages());
|
||||||
|
|
||||||
|
for (int i : pagesToNumberList) {
|
||||||
|
PDPage page = document.getPage(i);
|
||||||
|
PDRectangle pageSize = page.getMediaBox();
|
||||||
|
|
||||||
|
String text = customText != null ? customText.replace("{n}", String.valueOf(pageNumber)).replace("{total}", String.valueOf(document.getNumberOfPages())).replace("{filename}", file.getOriginalFilename().replaceFirst("[.][^.]+$", "")) : String.valueOf(pageNumber);
|
||||||
|
|
||||||
|
float x, y;
|
||||||
|
|
||||||
|
int xGroup = (position - 1) % 3;
|
||||||
|
int yGroup = 2 - (position - 1) / 3;
|
||||||
|
|
||||||
|
switch (xGroup) {
|
||||||
|
case 0: // left
|
||||||
|
x = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
|
||||||
|
break;
|
||||||
|
case 1: // center
|
||||||
|
x = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
|
||||||
|
break;
|
||||||
|
default: // right
|
||||||
|
x = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (yGroup) {
|
||||||
|
case 0: // bottom
|
||||||
|
y = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
|
||||||
|
break;
|
||||||
|
case 1: // middle
|
||||||
|
y = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
|
||||||
|
break;
|
||||||
|
default: // top
|
||||||
|
y = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);
|
||||||
|
contentStream.beginText();
|
||||||
|
contentStream.setFont(font, fontSize);
|
||||||
|
contentStream.newLineAtOffset(x, y);
|
||||||
|
contentStream.showText(text);
|
||||||
|
contentStream.endText();
|
||||||
|
contentStream.close();
|
||||||
|
|
||||||
|
pageNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
document.save(baos);
|
||||||
|
document.close();
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", MediaType.APPLICATION_PDF);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -9,20 +9,22 @@ import java.util.List;
|
||||||
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;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class RepairController {
|
public class RepairController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
|
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
|
||||||
|
@ -32,11 +34,8 @@ public class RepairController {
|
||||||
summary = "Repair a PDF file",
|
summary = "Repair a PDF file",
|
||||||
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO"
|
description = "This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> repairPdf(
|
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) throws IOException, InterruptedException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file to be repaired", required = true)
|
|
||||||
MultipartFile inputFile) throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
inputFile.transferTo(tempInputFile.toFile());
|
inputFile.transferTo(tempInputFile.toFile());
|
|
@ -0,0 +1,61 @@
|
||||||
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
|
public class ShowJavascript {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
||||||
|
public ResponseEntity<byte[]> extractHeader(@ModelAttribute PDFFile request) throws Exception {
|
||||||
|
MultipartFile inputFile = request.getFileInput();
|
||||||
|
String script = "";
|
||||||
|
|
||||||
|
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
||||||
|
|
||||||
|
if(document.getDocumentCatalog() != null && document.getDocumentCatalog().getNames() != null) {
|
||||||
|
PDNameTreeNode<PDActionJavaScript> jsTree = document.getDocumentCatalog().getNames().getJavaScript();
|
||||||
|
|
||||||
|
if (jsTree != null) {
|
||||||
|
Map<String, PDActionJavaScript> jsEntries = jsTree.getNames();
|
||||||
|
|
||||||
|
for (Map.Entry<String, PDActionJavaScript> entry : jsEntries.entrySet()) {
|
||||||
|
String name = entry.getKey();
|
||||||
|
PDActionJavaScript jsAction = entry.getValue();
|
||||||
|
String jsCodeStr = jsAction.getAction();
|
||||||
|
|
||||||
|
script += "// File: " + inputFile.getOriginalFilename() + ", Script: " + name + "\n" + jsCodeStr + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (script.isEmpty()) {
|
||||||
|
script = "PDF '" + inputFile.getOriginalFilename() + "' does not contain Javascript";
|
||||||
|
}
|
||||||
|
|
||||||
|
return WebResponseUtils.bytesToWebResponse(script.getBytes(StandardCharsets.UTF_8), inputFile.getOriginalFilename() + ".js");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,174 +0,0 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
||||||
import org.apache.pdfbox.pdmodel.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream.*;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.http.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import io.swagger.v3.oas.annotations.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.*;
|
|
||||||
import io.swagger.v3.oas.annotations.parameters.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
|
||||||
import org.apache.tomcat.util.http.ResponseUtil;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import com.itextpdf.io.font.constants.StandardFonts;
|
|
||||||
import com.itextpdf.kernel.font.PdfFont;
|
|
||||||
import com.itextpdf.kernel.font.PdfFontFactory;
|
|
||||||
import com.itextpdf.kernel.geom.Rectangle;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfReader;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfPage;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
|
||||||
import com.itextpdf.layout.Canvas;
|
|
||||||
import com.itextpdf.layout.element.Paragraph;
|
|
||||||
import com.itextpdf.layout.properties.TextAlignment;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
|
||||||
public class PageNumbersController {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
|
|
||||||
|
|
||||||
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
|
||||||
@Operation(summary = "Add page numbers to a PDF document", description = "This operation takes an input PDF file and adds page numbers to it. Input:PDF Output:PDF Type:SISO")
|
|
||||||
public ResponseEntity<byte[]> addPageNumbers(
|
|
||||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
|
||||||
@Parameter(description = "Custom margin: small/medium/large", required = true, schema = @Schema(type = "string", allowableValues = {"small", "medium", "large"})) @RequestParam("customMargin") String customMargin,
|
|
||||||
@Parameter(description = "Position: 1 of 9 positions", required = true, schema = @Schema(type = "integer", minimum = "1", maximum = "9")) @RequestParam("position") int position,
|
|
||||||
@Parameter(description = "Starting number", required = true, schema = @Schema(type = "integer", minimum = "1")) @RequestParam("startingNumber") int startingNumber,
|
|
||||||
@Parameter(description = "Which pages to number, default all", required = false, schema = @Schema(type = "string")) @RequestParam(value = "pagesToNumber", required = false) String pagesToNumber,
|
|
||||||
@Parameter(description = "Custom text: defaults to just number but can have things like \"Page {n} of {p}\"", required = false, schema = @Schema(type = "string")) @RequestParam(value = "customText", required = false) String customText)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
byte[] fileBytes = file.getBytes();
|
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
|
|
||||||
|
|
||||||
int pageNumber = startingNumber;
|
|
||||||
float marginFactor;
|
|
||||||
switch (customMargin.toLowerCase()) {
|
|
||||||
case "small":
|
|
||||||
marginFactor = 0.02f;
|
|
||||||
break;
|
|
||||||
case "medium":
|
|
||||||
marginFactor = 0.035f;
|
|
||||||
break;
|
|
||||||
case "large":
|
|
||||||
marginFactor = 0.05f;
|
|
||||||
break;
|
|
||||||
case "x-large":
|
|
||||||
marginFactor = 0.1f;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
marginFactor = 0.035f;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
float fontSize = 12.0f;
|
|
||||||
|
|
||||||
PdfReader reader = new PdfReader(bais);
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
PdfWriter writer = new PdfWriter(baos);
|
|
||||||
|
|
||||||
PdfDocument pdfDoc = new PdfDocument(reader, writer);
|
|
||||||
|
|
||||||
List<Integer> pagesToNumberList = GeneralUtils.parsePageList(pagesToNumber.split(","), pdfDoc.getNumberOfPages());
|
|
||||||
|
|
||||||
for (int i : pagesToNumberList) {
|
|
||||||
PdfPage page = pdfDoc.getPage(i+1);
|
|
||||||
Rectangle pageSize = page.getPageSize();
|
|
||||||
PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), pdfDoc);
|
|
||||||
|
|
||||||
String text = customText != null ? customText.replace("{n}", String.valueOf(pageNumber)).replace("{total}", String.valueOf(pdfDoc.getNumberOfPages())) : String.valueOf(pageNumber);
|
|
||||||
|
|
||||||
PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA);
|
|
||||||
float textWidth = font.getWidth(text, fontSize);
|
|
||||||
float textHeight = font.getAscent(text, fontSize) - font.getDescent(text, fontSize);
|
|
||||||
|
|
||||||
float x, y;
|
|
||||||
TextAlignment alignment;
|
|
||||||
|
|
||||||
int xGroup = (position - 1) % 3;
|
|
||||||
int yGroup = 2 - (position - 1) / 3;
|
|
||||||
|
|
||||||
switch (xGroup) {
|
|
||||||
case 0: // left
|
|
||||||
x = pageSize.getLeft() + marginFactor * pageSize.getWidth();
|
|
||||||
alignment = TextAlignment.LEFT;
|
|
||||||
break;
|
|
||||||
case 1: // center
|
|
||||||
x = pageSize.getLeft() + (pageSize.getWidth()) / 2;
|
|
||||||
alignment = TextAlignment.CENTER;
|
|
||||||
break;
|
|
||||||
default: // right
|
|
||||||
x = pageSize.getRight() - marginFactor * pageSize.getWidth();
|
|
||||||
alignment = TextAlignment.RIGHT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (yGroup) {
|
|
||||||
case 0: // bottom
|
|
||||||
y = pageSize.getBottom() + marginFactor * pageSize.getHeight();
|
|
||||||
break;
|
|
||||||
case 1: // middle
|
|
||||||
y = pageSize.getBottom() + (pageSize.getHeight() ) / 2;
|
|
||||||
break;
|
|
||||||
default: // top
|
|
||||||
y = pageSize.getTop() - marginFactor * pageSize.getHeight();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
new Canvas(pdfCanvas, page.getPageSize())
|
|
||||||
.showTextAligned(new Paragraph(text).setFont(font).setFontSize(fontSize), x, y, alignment);
|
|
||||||
|
|
||||||
pageNumber++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pdfDoc.close();
|
|
||||||
byte[] resultBytes = baos.toByteArray();
|
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(resultBytes, URLEncoder.encode(file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_numbersAdded.pdf", "UTF-8"), MediaType.APPLICATION_PDF);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,141 +0,0 @@
|
||||||
package stirling.software.SPDF.controller.api.other;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import stirling.software.SPDF.utils.GeneralUtils;
|
|
||||||
import stirling.software.SPDF.utils.PdfUtils;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
||||||
import org.apache.pdfbox.pdmodel.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream.*;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.http.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import io.swagger.v3.oas.annotations.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.*;
|
|
||||||
import io.swagger.v3.oas.annotations.parameters.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
|
||||||
import org.apache.pdfbox.text.TextPosition;
|
|
||||||
import org.apache.tomcat.util.http.ResponseUtil;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
import com.itextpdf.io.font.constants.StandardFonts;
|
|
||||||
import com.itextpdf.kernel.font.PdfFont;
|
|
||||||
import com.itextpdf.kernel.font.PdfFontFactory;
|
|
||||||
import com.itextpdf.kernel.geom.Rectangle;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfReader;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfStream;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfArray;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDictionary;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfName;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfObject;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfPage;
|
|
||||||
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
|
||||||
import com.itextpdf.layout.Canvas;
|
|
||||||
import com.itextpdf.layout.element.Paragraph;
|
|
||||||
import com.itextpdf.layout.properties.TextAlignment;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import org.apache.pdfbox.pdmodel.*;
|
|
||||||
import org.apache.pdfbox.text.*;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import io.swagger.v3.oas.annotations.*;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
@RestController
|
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
|
||||||
public class ShowJavascript {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
|
||||||
@Operation(summary = "Extract header from PDF file", description = "This endpoint accepts a PDF file and attempts to extract its title or header based on heuristics. Input:PDF Output:PDF Type:SISO")
|
|
||||||
public ResponseEntity<byte[]> extractHeader(
|
|
||||||
@RequestPart(value = "fileInput") @Parameter(description = "The input PDF file from which the javascript is to be extracted.", required = true) MultipartFile inputFile)
|
|
||||||
throws Exception {
|
|
||||||
|
|
||||||
try (
|
|
||||||
PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream()))
|
|
||||||
) {
|
|
||||||
|
|
||||||
String name = "";
|
|
||||||
String script = "";
|
|
||||||
String entryName = "File: "+inputFile.getOriginalFilename() + ", Script: ";
|
|
||||||
//Javascript
|
|
||||||
PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names);
|
|
||||||
if (namesDict != null) {
|
|
||||||
PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript);
|
|
||||||
if (javascriptDict != null) {
|
|
||||||
|
|
||||||
PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names);
|
|
||||||
for (int i = 0; i < namesArray.size(); i += 2) {
|
|
||||||
if(namesArray.getAsString(i) != null)
|
|
||||||
name = namesArray.getAsString(i).toString();
|
|
||||||
|
|
||||||
PdfObject jsCode = namesArray.get(i+1);
|
|
||||||
if (jsCode instanceof PdfStream) {
|
|
||||||
byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes();
|
|
||||||
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
|
|
||||||
script = "//" + entryName + name + "\n" +jsCodeStr;
|
|
||||||
|
|
||||||
} else if (jsCode instanceof PdfDictionary) {
|
|
||||||
// If the JS code is in a dictionary, you'll need to know the key to use.
|
|
||||||
// Assuming the key is PdfName.JS:
|
|
||||||
PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS);
|
|
||||||
if (jsCodeStream != null) {
|
|
||||||
byte[] jsCodeBytes = jsCodeStream.getBytes();
|
|
||||||
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
|
|
||||||
script = "//" + entryName + name + "\n" +jsCodeStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(script.equals("")) {
|
|
||||||
script = "PDF '" +inputFile.getOriginalFilename() + "' does not contain Javascript";
|
|
||||||
}
|
|
||||||
return WebResponseUtils.bytesToWebResponse(script.getBytes(), name + ".js");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -3,8 +3,10 @@ package stirling.software.SPDF.controller.api.pipeline;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -20,8 +22,7 @@ import java.util.stream.Stream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
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;
|
||||||
|
@ -36,9 +37,9 @@ import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
@ -47,11 +48,14 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.PipelineConfig;
|
import stirling.software.SPDF.model.PipelineConfig;
|
||||||
import stirling.software.SPDF.model.PipelineOperation;
|
import stirling.software.SPDF.model.PipelineOperation;
|
||||||
|
import stirling.software.SPDF.model.api.HandleDataRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/pipeline")
|
||||||
@Tag(name = "Pipeline", description = "Pipeline APIs")
|
@Tag(name = "Pipeline", description = "Pipeline APIs")
|
||||||
public class PipelineController {
|
public class PipelineController {
|
||||||
|
|
||||||
|
@ -91,6 +95,10 @@ public class PipelineController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
|
||||||
private void handleDirectory(Path dir) throws Exception {
|
private void handleDirectory(Path dir) throws Exception {
|
||||||
logger.info("Handling directory: {}", dir);
|
logger.info("Handling directory: {}", dir);
|
||||||
Path jsonFile = dir.resolve(jsonFileName);
|
Path jsonFile = dir.resolve(jsonFileName);
|
||||||
|
@ -182,8 +190,7 @@ public class PipelineController {
|
||||||
// {filename} {folder} {date} {tmime} {pipeline}
|
// {filename} {folder} {date} {tmime} {pipeline}
|
||||||
String outputDir = config.getOutputDir();
|
String outputDir = config.getOutputDir();
|
||||||
|
|
||||||
// Check if the environment variable 'automatedOutputFolder' is set
|
String outputFolder = applicationProperties.getAutoPipeline().getOutputFolder();
|
||||||
String outputFolder = System.getenv("automatedOutputFolder");
|
|
||||||
|
|
||||||
if (outputFolder == null || outputFolder.isEmpty()) {
|
if (outputFolder == null || outputFolder.isEmpty()) {
|
||||||
// If the environment variable is not set, use the default value
|
// If the environment variable is not set, use the default value
|
||||||
|
@ -413,8 +420,9 @@ public class PipelineController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/handleData")
|
@PostMapping("/handleData")
|
||||||
public ResponseEntity<byte[]> handleData(@RequestPart("fileInput") MultipartFile[] files,
|
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request) {
|
||||||
@RequestParam("json") String jsonString) {
|
MultipartFile[] files = request.getFileInputs();
|
||||||
|
String jsonString = request.getJsonString();
|
||||||
logger.info("Received POST request to /handleData with {} files", files.length);
|
logger.info("Received POST request to /handleData with {} files", files.length);
|
||||||
try {
|
try {
|
||||||
List<Resource> outputFiles = handleFiles(files, jsonString);
|
List<Resource> outputFiles = handleFiles(files, jsonString);
|
||||||
|
|
|
@ -3,57 +3,61 @@ package stirling.software.SPDF.controller.api.security;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.Principal;
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Arrays;
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
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.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.operator.ContentSigner;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
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;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
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 com.itextpdf.io.font.constants.StandardFonts;
|
|
||||||
import com.itextpdf.kernel.font.PdfFont;
|
|
||||||
import com.itextpdf.kernel.font.PdfFontFactory;
|
|
||||||
import com.itextpdf.kernel.geom.Rectangle;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfPage;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfReader;
|
|
||||||
import com.itextpdf.kernel.pdf.StampingProperties;
|
|
||||||
import com.itextpdf.signatures.BouncyCastleDigest;
|
|
||||||
import com.itextpdf.signatures.DigestAlgorithms;
|
|
||||||
import com.itextpdf.signatures.IExternalDigest;
|
|
||||||
import com.itextpdf.signatures.IExternalSignature;
|
|
||||||
import com.itextpdf.signatures.PdfPKCS7;
|
|
||||||
import com.itextpdf.signatures.PdfSignatureAppearance;
|
|
||||||
import com.itextpdf.signatures.PdfSigner;
|
|
||||||
import com.itextpdf.signatures.PrivateKeySignature;
|
|
||||||
import com.itextpdf.signatures.SignatureUtil;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/security")
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class CertSignController {
|
public class CertSignController {
|
||||||
|
|
||||||
|
@ -64,66 +68,34 @@ public class CertSignController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
@PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
|
||||||
@Operation(summary = "Sign PDF with a Digital Certificate",
|
@Operation(summary = "Sign PDF with a Digital Certificate", description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO")
|
||||||
description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:MF-SISO")
|
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) throws Exception {
|
||||||
public ResponseEntity<byte[]> signPDF(
|
MultipartFile pdf = request.getFileInput();
|
||||||
@RequestPart(required = true, value = "fileInput")
|
String certType = request.getCertType();
|
||||||
@Parameter(description = "The input PDF file to be signed")
|
MultipartFile privateKeyFile = request.getPrivateKeyFile();
|
||||||
MultipartFile pdf,
|
MultipartFile certFile = request.getCertFile();
|
||||||
|
MultipartFile p12File = request.getP12File();
|
||||||
@RequestParam(value = "certType", required = false)
|
String password = request.getPassword();
|
||||||
@Parameter(description = "The type of the digital certificate", schema = @Schema(allowableValues = {"PKCS12", "PEM"}))
|
Boolean showSignature = request.isShowSignature();
|
||||||
String certType,
|
String reason = request.getReason();
|
||||||
|
String location = request.getLocation();
|
||||||
@RequestParam(value = "key", required = false)
|
String name = request.getName();
|
||||||
@Parameter(description = "The private key for the digital certificate (required for PEM type certificates)")
|
Integer pageNumber = request.getPageNumber();
|
||||||
MultipartFile privateKeyFile,
|
|
||||||
|
|
||||||
@RequestParam(value = "cert", required = false)
|
|
||||||
@Parameter(description = "The digital certificate (required for PEM type certificates)")
|
|
||||||
MultipartFile certFile,
|
|
||||||
|
|
||||||
@RequestParam(value = "p12", required = false)
|
|
||||||
@Parameter(description = "The PKCS12 keystore file (required for PKCS12 type certificates)")
|
|
||||||
MultipartFile p12File,
|
|
||||||
|
|
||||||
@RequestParam(value = "password", required = false)
|
|
||||||
@Parameter(description = "The password for the keystore or the private key")
|
|
||||||
String password,
|
|
||||||
|
|
||||||
@RequestParam(value = "showSignature", required = false)
|
|
||||||
@Parameter(description = "Whether to visually show the signature in the PDF file")
|
|
||||||
Boolean showSignature,
|
|
||||||
|
|
||||||
@RequestParam(value = "reason", required = false)
|
|
||||||
@Parameter(description = "The reason for signing the PDF")
|
|
||||||
String reason,
|
|
||||||
|
|
||||||
@RequestParam(value = "location", required = false)
|
|
||||||
@Parameter(description = "The location where the PDF is signed")
|
|
||||||
String location,
|
|
||||||
|
|
||||||
@RequestParam(value = "name", required = false)
|
|
||||||
@Parameter(description = "The name of the signer")
|
|
||||||
String name,
|
|
||||||
|
|
||||||
@RequestParam(value = "pageNumber", required = false)
|
|
||||||
@Parameter(description = "The page number where the signature should be visible. This is required if showSignature is set to true")
|
|
||||||
Integer pageNumber) throws Exception {
|
|
||||||
|
|
||||||
BouncyCastleProvider provider = new BouncyCastleProvider();
|
|
||||||
Security.addProvider(provider);
|
|
||||||
|
|
||||||
PrivateKey privateKey = null;
|
PrivateKey privateKey = null;
|
||||||
X509Certificate cert = null;
|
X509Certificate cert = null;
|
||||||
|
|
||||||
if (certType != null) {
|
if (certType != null) {
|
||||||
|
logger.info("Cert type provided: {}", certType);
|
||||||
switch (certType) {
|
switch (certType) {
|
||||||
case "PKCS12":
|
case "PKCS12":
|
||||||
if (p12File != null) {
|
if (p12File != null) {
|
||||||
KeyStore ks = KeyStore.getInstance("PKCS12");
|
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||||
ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray());
|
ks.load(new ByteArrayInputStream(p12File.getBytes()), password.toCharArray());
|
||||||
String alias = ks.aliases().nextElement();
|
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());
|
privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray());
|
||||||
cert = (X509Certificate) ks.getCertificate(alias);
|
cert = (X509Certificate) ks.getCertificate(alias);
|
||||||
}
|
}
|
||||||
|
@ -131,154 +103,148 @@ public class CertSignController {
|
||||||
case "PEM":
|
case "PEM":
|
||||||
if (privateKeyFile != null && certFile != null) {
|
if (privateKeyFile != null && certFile != null) {
|
||||||
// Load private key
|
// Load private key
|
||||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA", provider);
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
|
||||||
if (isPEM(privateKeyFile.getBytes())) {
|
if (isPEM(privateKeyFile.getBytes())) {
|
||||||
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes())));
|
privateKey = keyFactory
|
||||||
|
.generatePrivate(new PKCS8EncodedKeySpec(parsePEM(privateKeyFile.getBytes())));
|
||||||
} else {
|
} else {
|
||||||
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes()));
|
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyFile.getBytes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load certificate
|
// Load certificate
|
||||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509", provider);
|
CertificateFactory certFactory = CertificateFactory.getInstance("X.509",
|
||||||
|
BouncyCastleProvider.PROVIDER_NAME);
|
||||||
if (isPEM(certFile.getBytes())) {
|
if (isPEM(certFile.getBytes())) {
|
||||||
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes())));
|
cert = (X509Certificate) certFactory
|
||||||
|
.generateCertificate(new ByteArrayInputStream(parsePEM(certFile.getBytes())));
|
||||||
} else {
|
} else {
|
||||||
cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certFile.getBytes()));
|
cert = (X509Certificate) certFactory
|
||||||
|
.generateCertificate(new ByteArrayInputStream(certFile.getBytes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
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());
|
||||||
|
|
||||||
Principal principal = cert.getSubjectDN();
|
// Load the PDF
|
||||||
String dn = principal.getName();
|
try (PDDocument document = PDDocument.load(pdf.getBytes())) {
|
||||||
|
logger.info("Successfully loaded the provided PDF");
|
||||||
|
SignatureOptions signatureOptions = new SignatureOptions();
|
||||||
|
|
||||||
// Extract the "CN" (Common Name) field from the distinguished name (if it's present)
|
// If you want to show the signature
|
||||||
String cn = null;
|
|
||||||
for (String part : dn.split(",")) {
|
|
||||||
if (part.trim().startsWith("CN=")) {
|
|
||||||
cn = part.trim().substring("CN=".length());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the PDF reader and stamper
|
|
||||||
PdfReader reader = new PdfReader(new ByteArrayInputStream(pdf.getBytes()));
|
|
||||||
ByteArrayOutputStream signedPdf = new ByteArrayOutputStream();
|
|
||||||
PdfSigner signer = new PdfSigner(reader, signedPdf, new StampingProperties());
|
|
||||||
|
|
||||||
// Set up the signing appearance
|
|
||||||
PdfSignatureAppearance appearance = signer.getSignatureAppearance()
|
|
||||||
.setReason("Test")
|
|
||||||
.setLocation("TestLocation");
|
|
||||||
|
|
||||||
|
// ATTEMPT 2
|
||||||
if (showSignature != null && showSignature) {
|
if (showSignature != null && showSignature) {
|
||||||
float fontSize = 4; // the font size of the signature
|
PDPage page = document.getPage(pageNumber - 1);
|
||||||
float marginRight = 36; // Margin from the right
|
|
||||||
float marginBottom = 36; // Margin from the bottom
|
|
||||||
String signingDate = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z").format(new Date());
|
|
||||||
|
|
||||||
// Prepare the text for the digital signature
|
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
|
||||||
StringBuilder layer2TextBuilder = new StringBuilder(String.format("Digitally signed by: %s\nDate: %s",
|
if (acroForm == null) {
|
||||||
name != null ? name : "Unknown", signingDate));
|
acroForm = new PDAcroForm(document);
|
||||||
|
document.getDocumentCatalog().setAcroForm(acroForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new signature field and widget
|
||||||
|
|
||||||
|
PDSignatureField signatureField = new PDSignatureField(acroForm);
|
||||||
|
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()) {
|
if (reason != null && !reason.isEmpty()) {
|
||||||
layer2TextBuilder.append("\nReason: ").append(reason);
|
contentStream.showText("Reason: " + reason);
|
||||||
|
contentStream.newLineAtOffset(0, -15);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location != null && !location.isEmpty()) {
|
if (location != null && !location.isEmpty()) {
|
||||||
layer2TextBuilder.append("\nLocation: ").append(location);
|
contentStream.showText("Location: " + location);
|
||||||
|
contentStream.newLineAtOffset(0, -15);
|
||||||
}
|
}
|
||||||
String layer2Text = layer2TextBuilder.toString();
|
contentStream.endText();
|
||||||
// Get the PDF font and measure the width and height of the text block
|
|
||||||
PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD);
|
|
||||||
float textWidth = Arrays.stream(layer2Text.split("\n"))
|
|
||||||
.map(line -> font.getWidth(line, fontSize))
|
|
||||||
.max(Float::compare)
|
|
||||||
.orElse(0f);
|
|
||||||
int numLines = layer2Text.split("\n").length;
|
|
||||||
float textHeight = numLines * fontSize;
|
|
||||||
|
|
||||||
// Calculate the signature rectangle size
|
|
||||||
float sigWidth = textWidth + marginRight * 2;
|
|
||||||
float sigHeight = textHeight + marginBottom * 2;
|
|
||||||
|
|
||||||
// Get the page size
|
|
||||||
PdfPage page = signer.getDocument().getPage(1);
|
|
||||||
Rectangle pageSize = page.getPageSize();
|
|
||||||
|
|
||||||
// Define the position and dimension of the signature field
|
|
||||||
Rectangle rect = new Rectangle(
|
|
||||||
pageSize.getRight() - sigWidth - marginRight,
|
|
||||||
pageSize.getBottom() + marginBottom,
|
|
||||||
sigWidth,
|
|
||||||
sigHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
// Configure the appearance of the digital signature
|
|
||||||
appearance.setPageRect(rect)
|
|
||||||
.setContact(name != null ? name : "")
|
|
||||||
.setPageNumber(pageNumber)
|
|
||||||
.setReason(reason != null ? reason : "")
|
|
||||||
.setLocation(location != null ? location : "")
|
|
||||||
.setReuseAppearance(false)
|
|
||||||
.setLayer2Text(layer2Text.toString());
|
|
||||||
|
|
||||||
signer.setFieldName("sig");
|
|
||||||
} else {
|
|
||||||
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the signer
|
// Add the widget annotation to the page
|
||||||
PrivateKeySignature pks = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName());
|
page.getAnnotations().add(widget);
|
||||||
IExternalSignature pss = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA256, provider.getName());
|
|
||||||
IExternalDigest digest = new BouncyCastleDigest();
|
|
||||||
|
|
||||||
// Call iTex7 to sign the PDF
|
// Add the signature field to the acroform
|
||||||
signer.signDetached(digest, pks, new Certificate[] {cert}, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
|
acroForm.getFields().add(signatureField);
|
||||||
|
|
||||||
|
// Handle multiple signatures by ensuring a unique field name
|
||||||
System.out.println("Signed PDF size: " + signedPdf.size());
|
String baseFieldName = "Signature";
|
||||||
|
String signatureFieldName = baseFieldName;
|
||||||
System.out.println("PDF signed = " + isPdfSigned(signedPdf.toByteArray()));
|
int suffix = 1;
|
||||||
return WebResponseUtils.bytesToWebResponse(signedPdf.toByteArray(), "example.pdf");
|
while (acroForm.getField(signatureFieldName) != null) {
|
||||||
|
suffix++;
|
||||||
|
signatureFieldName = baseFieldName + suffix;
|
||||||
|
}
|
||||||
|
signatureField.setPartialName(signatureFieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPdfSigned(byte[] pdfData) throws IOException {
|
document.addSignature(signature, signatureOptions);
|
||||||
InputStream pdfStream = new ByteArrayInputStream(pdfData);
|
logger.info("Signature added to the PDF document");
|
||||||
PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfStream));
|
// External signing
|
||||||
SignatureUtil signatureUtil = new SignatureUtil(pdfDoc);
|
ExternalSigningSupport externalSigning = document
|
||||||
List<String> names = signatureUtil.getSignatureNames();
|
.saveIncrementalForExternalSigning(new ByteArrayOutputStream());
|
||||||
|
|
||||||
boolean isSigned = false;
|
byte[] content = IOUtils.toByteArray(externalSigning.getContent());
|
||||||
|
|
||||||
for (String name : names) {
|
// Using BouncyCastle to sign
|
||||||
PdfPKCS7 pkcs7 = signatureUtil.readSignatureData(name);
|
CMSTypedData cmsData = new CMSProcessableByteArray(content);
|
||||||
if (pkcs7 != null) {
|
|
||||||
System.out.println("Signature found.");
|
|
||||||
|
|
||||||
// Log certificate details
|
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
|
||||||
Certificate[] signChain = pkcs7.getSignCertificateChain();
|
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
|
||||||
for (Certificate cert : signChain) {
|
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(privateKey);
|
||||||
if (cert instanceof X509Certificate) {
|
|
||||||
X509Certificate x509 = (X509Certificate) cert;
|
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
|
||||||
System.out.println("Certificate Details:");
|
new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build())
|
||||||
System.out.println("Subject: " + x509.getSubjectDN());
|
.build(signer, cert));
|
||||||
System.out.println("Issuer: " + x509.getIssuerDN());
|
|
||||||
System.out.println("Serial: " + x509.getSerialNumber());
|
gen.addCertificates(new JcaCertStore(Collections.singletonList(cert)));
|
||||||
System.out.println("Not Before: " + x509.getNotBefore());
|
CMSSignedData signedData = gen.generate(cmsData, false);
|
||||||
System.out.println("Not After: " + x509.getNotAfter());
|
|
||||||
|
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) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
isSigned = true;
|
return null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pdfDoc.close();
|
|
||||||
|
|
||||||
return isSigned;
|
|
||||||
}
|
|
||||||
private byte[] parsePEM(byte[] content) throws IOException {
|
private byte[] parsePEM(byte[] content) throws IOException {
|
||||||
PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content)));
|
PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(content)));
|
||||||
return pemReader.readPemObject().getContent();
|
return pemReader.readPemObject().getContent();
|
||||||
|
@ -289,8 +255,4 @@ public boolean isPdfSigned(byte[] pdfData) throws IOException {
|
||||||
return contentStr.contains("-----BEGIN") && contentStr.contains("-----END");
|
return contentStr.contains("-----BEGIN") && contentStr.contains("-----END");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +1,81 @@
|
||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import org.apache.pdfbox.cos.COSArray;
|
import java.io.ByteArrayOutputStream;
|
||||||
import org.apache.pdfbox.cos.COSBase;
|
|
||||||
import org.apache.pdfbox.cos.COSDictionary;
|
|
||||||
import org.apache.pdfbox.cos.COSName;
|
|
||||||
import org.apache.pdfbox.cos.COSString;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
|
||||||
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
|
|
||||||
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode;
|
|
||||||
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
|
|
||||||
import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
|
||||||
|
|
||||||
import com.itextpdf.kernel.pdf.PdfObject;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfOutline;
|
|
||||||
import com.itextpdf.forms.PdfAcroForm;
|
|
||||||
import com.itextpdf.forms.fields.PdfFormField;
|
|
||||||
import com.itextpdf.kernel.geom.Rectangle;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfArray;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfCatalog;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDictionary;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfDocumentInfo;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfEncryption;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfReader;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfResources;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfStream;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfString;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfName;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfViewerPreferences;
|
|
||||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
|
||||||
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
|
|
||||||
import com.itextpdf.kernel.pdf.annot.PdfFileAttachmentAnnotation;
|
|
||||||
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
|
|
||||||
import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation;
|
|
||||||
import com.itextpdf.kernel.pdf.layer.PdfLayer;
|
|
||||||
import com.itextpdf.kernel.pdf.layer.PdfOCProperties;
|
|
||||||
import com.itextpdf.kernel.xmp.XMPException;
|
|
||||||
import com.itextpdf.kernel.xmp.XMPMeta;
|
|
||||||
import com.itextpdf.kernel.xmp.XMPMetaFactory;
|
|
||||||
import com.itextpdf.kernel.xmp.options.SerializeOptions;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.HashSet;
|
|
||||||
|
import org.apache.pdfbox.cos.COSDocument;
|
||||||
|
import org.apache.pdfbox.cos.COSInputStream;
|
||||||
|
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.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDJavascriptNameTreeNode;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||||
|
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
|
||||||
|
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode;
|
||||||
|
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
|
||||||
|
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
||||||
|
import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
|
import org.apache.xmpbox.XMPMetadata;
|
||||||
|
import org.apache.xmpbox.xml.DomXmpParser;
|
||||||
|
import org.apache.xmpbox.xml.XmpParsingException;
|
||||||
|
import org.apache.xmpbox.xml.XmpSerializer;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/security")
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class GetInfoOnPDF {
|
public class GetInfoOnPDF {
|
||||||
|
|
||||||
|
@ -80,14 +83,11 @@ public class GetInfoOnPDF {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
|
||||||
@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(
|
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request)
|
||||||
@RequestPart(required = true, value = "fileInput")
|
|
||||||
@Parameter(description = "The input PDF file to get info on", required = true) MultipartFile inputFile)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
MultipartFile inputFile = request.getFileInput();
|
||||||
try (
|
try (
|
||||||
PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream());
|
PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream());
|
||||||
PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream()))
|
|
||||||
) {
|
) {
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
ObjectNode jsonOutput = objectMapper.createObjectNode();
|
ObjectNode jsonOutput = objectMapper.createObjectNode();
|
||||||
|
@ -135,20 +135,16 @@ public class GetInfoOnPDF {
|
||||||
boolean hasCompression = false;
|
boolean hasCompression = false;
|
||||||
String compressionType = "None";
|
String compressionType = "None";
|
||||||
|
|
||||||
// Check for object streams
|
COSDocument cosDoc = pdfBoxDoc.getDocument();
|
||||||
for (int i = 1; i <= itextDoc.getNumberOfPdfObjects(); i++) {
|
for (COSObject cosObject : cosDoc.getObjects()) {
|
||||||
PdfObject obj = itextDoc.getPdfObject(i);
|
if (cosObject.getObject() instanceof COSStream) {
|
||||||
if (obj != null && obj.isStream() && ((PdfStream) obj).get(PdfName.Type) == PdfName.ObjStm) {
|
COSStream cosStream = (COSStream) cosObject.getObject();
|
||||||
|
if (COSName.OBJ_STM.equals(cosStream.getItem(COSName.TYPE))) {
|
||||||
hasCompression = true;
|
hasCompression = true;
|
||||||
compressionType = "Object Streams";
|
compressionType = "Object Streams";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not compressed using object streams, check for compressed Xref tables
|
|
||||||
if (!hasCompression && itextDoc.getReader().hasRebuiltXref()) {
|
|
||||||
hasCompression = true;
|
|
||||||
compressionType = "Compressed Xref or Rebuilt Xref";
|
|
||||||
}
|
}
|
||||||
basicInfo.put("Compression", hasCompression);
|
basicInfo.put("Compression", hasCompression);
|
||||||
if(hasCompression)
|
if(hasCompression)
|
||||||
|
@ -159,9 +155,8 @@ public class GetInfoOnPDF {
|
||||||
basicInfo.put("Number of pages", pdfBoxDoc.getNumberOfPages());
|
basicInfo.put("Number of pages", pdfBoxDoc.getNumberOfPages());
|
||||||
|
|
||||||
|
|
||||||
// Page Mode using iText7
|
PDDocumentCatalog catalog = pdfBoxDoc.getDocumentCatalog();
|
||||||
PdfCatalog catalog = itextDoc.getCatalog();
|
String pageMode = catalog.getPageMode().name();
|
||||||
PdfName pageMode = catalog.getPdfObject().getAsName(PdfName.PageMode);
|
|
||||||
|
|
||||||
// Document Information using PDFBox
|
// Document Information using PDFBox
|
||||||
docInfoNode.put("PDF version", pdfBoxDoc.getVersion());
|
docInfoNode.put("PDF version", pdfBoxDoc.getVersion());
|
||||||
|
@ -172,11 +167,12 @@ public class GetInfoOnPDF {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PdfAcroForm acroForm = PdfAcroForm.getAcroForm(itextDoc, false);
|
PDAcroForm acroForm = pdfBoxDoc.getDocumentCatalog().getAcroForm();
|
||||||
|
|
||||||
ObjectNode formFieldsNode = objectMapper.createObjectNode();
|
ObjectNode formFieldsNode = objectMapper.createObjectNode();
|
||||||
if (acroForm != null) {
|
if (acroForm != null) {
|
||||||
for (Map.Entry<String, PdfFormField> entry : acroForm.getFormFields().entrySet()) {
|
for (PDField field : acroForm.getFieldTree()) {
|
||||||
formFieldsNode.put(entry.getKey(), entry.getValue().getValueAsString());
|
formFieldsNode.put(field.getFullyQualifiedName(), field.getValueAsString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsonOutput.set("FormFields", formFieldsNode);
|
jsonOutput.set("FormFields", formFieldsNode);
|
||||||
|
@ -185,36 +181,42 @@ public class GetInfoOnPDF {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//embeed files TODO size
|
|
||||||
ArrayNode embeddedFilesArray = objectMapper.createArrayNode();
|
|
||||||
if(itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) != null)
|
|
||||||
{
|
|
||||||
PdfDictionary embeddedFiles = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names)
|
|
||||||
.getAsDictionary(PdfName.EmbeddedFiles);
|
|
||||||
if (embeddedFiles != null) {
|
|
||||||
|
|
||||||
PdfArray namesArray = embeddedFiles.getAsArray(PdfName.Names);
|
//embeed files TODO size
|
||||||
if(namesArray != null) {
|
if(catalog.getNames() != null) {
|
||||||
for (int i = 0; i < namesArray.size(); i += 2) {
|
PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles();
|
||||||
|
|
||||||
|
ArrayNode embeddedFilesArray = objectMapper.createArrayNode();
|
||||||
|
if (efTree != null) {
|
||||||
|
Map<String, PDComplexFileSpecification> efMap = efTree.getNames();
|
||||||
|
if (efMap != null) {
|
||||||
|
for (Map.Entry<String, PDComplexFileSpecification> entry : efMap.entrySet()) {
|
||||||
ObjectNode embeddedFileNode = objectMapper.createObjectNode();
|
ObjectNode embeddedFileNode = objectMapper.createObjectNode();
|
||||||
embeddedFileNode.put("Name", namesArray.getAsString(i).toString());
|
embeddedFileNode.put("Name", entry.getKey());
|
||||||
// Add other details if required
|
PDEmbeddedFile embeddedFile = entry.getValue().getEmbeddedFile();
|
||||||
|
if (embeddedFile != null) {
|
||||||
|
embeddedFileNode.put("FileSize", embeddedFile.getLength()); // size in bytes
|
||||||
|
}
|
||||||
embeddedFilesArray.add(embeddedFileNode);
|
embeddedFilesArray.add(embeddedFileNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
other.set("EmbeddedFiles", embeddedFilesArray);
|
other.set("EmbeddedFiles", embeddedFilesArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//attachments TODO size
|
//attachments TODO size
|
||||||
ArrayNode attachmentsArray = objectMapper.createArrayNode();
|
ArrayNode attachmentsArray = objectMapper.createArrayNode();
|
||||||
for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) {
|
for (PDPage page : pdfBoxDoc.getPages()) {
|
||||||
for (PdfAnnotation annotation : itextDoc.getPage(pageNum).getAnnotations()) {
|
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||||
if (annotation instanceof PdfFileAttachmentAnnotation) {
|
if (annotation instanceof PDAnnotationFileAttachment) {
|
||||||
|
PDAnnotationFileAttachment fileAttachmentAnnotation = (PDAnnotationFileAttachment) annotation;
|
||||||
|
|
||||||
ObjectNode attachmentNode = objectMapper.createObjectNode();
|
ObjectNode attachmentNode = objectMapper.createObjectNode();
|
||||||
attachmentNode.put("Name", ((PdfFileAttachmentAnnotation) annotation).getName().toString());
|
attachmentNode.put("Name", fileAttachmentAnnotation.getAttachmentName());
|
||||||
attachmentNode.put("Description", annotation.getContents().getValue());
|
attachmentNode.put("Description", fileAttachmentAnnotation.getContents());
|
||||||
|
|
||||||
attachmentsArray.add(attachmentNode);
|
attachmentsArray.add(attachmentNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,54 +224,49 @@ public class GetInfoOnPDF {
|
||||||
other.set("Attachments", attachmentsArray);
|
other.set("Attachments", attachmentsArray);
|
||||||
|
|
||||||
//Javascript
|
//Javascript
|
||||||
PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names);
|
PDDocumentNameDictionary namesDict = catalog.getNames();
|
||||||
ArrayNode javascriptArray = objectMapper.createArrayNode();
|
ArrayNode javascriptArray = objectMapper.createArrayNode();
|
||||||
|
|
||||||
if (namesDict != null) {
|
if (namesDict != null) {
|
||||||
PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript);
|
PDJavascriptNameTreeNode javascriptDict = namesDict.getJavaScript();
|
||||||
if (javascriptDict != null) {
|
if (javascriptDict != null) {
|
||||||
|
try {
|
||||||
|
Map<String, PDActionJavaScript> jsEntries = javascriptDict.getNames();
|
||||||
|
|
||||||
PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names);
|
for (Map.Entry<String, PDActionJavaScript> entry : jsEntries.entrySet()) {
|
||||||
for (int i = 0; i < namesArray.size(); i += 2) {
|
|
||||||
ObjectNode jsNode = objectMapper.createObjectNode();
|
ObjectNode jsNode = objectMapper.createObjectNode();
|
||||||
if(namesArray.getAsString(i) != null)
|
jsNode.put("JS Name", entry.getKey());
|
||||||
jsNode.put("JS Name", namesArray.getAsString(i).toString());
|
|
||||||
|
|
||||||
// Here we check for a PdfStream object and retrieve the JS code from it
|
PDActionJavaScript jsAction = entry.getValue();
|
||||||
PdfObject jsCode = namesArray.get(i+1);
|
if (jsAction != null) {
|
||||||
if (jsCode instanceof PdfStream) {
|
String jsCodeStr = jsAction.getAction();
|
||||||
byte[] jsCodeBytes = ((PdfStream)jsCode).getBytes();
|
if (jsCodeStr != null) {
|
||||||
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
|
|
||||||
jsNode.put("JS Script Length", jsCodeStr.length());
|
jsNode.put("JS Script Length", jsCodeStr.length());
|
||||||
} else if (jsCode instanceof PdfDictionary) {
|
|
||||||
// If the JS code is in a dictionary, you'll need to know the key to use.
|
|
||||||
// Assuming the key is PdfName.JS:
|
|
||||||
PdfStream jsCodeStream = ((PdfDictionary)jsCode).getAsStream(PdfName.JS);
|
|
||||||
if (jsCodeStream != null) {
|
|
||||||
byte[] jsCodeBytes = jsCodeStream.getBytes();
|
|
||||||
String jsCodeStr = new String(jsCodeBytes, StandardCharsets.UTF_8);
|
|
||||||
jsNode.put("JS Script Character Length", jsCodeStr.length());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
javascriptArray.add(jsNode);
|
javascriptArray.add(jsNode);
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
other.set("JavaScript", javascriptArray);
|
other.set("JavaScript", javascriptArray);
|
||||||
|
|
||||||
//TODO size
|
|
||||||
PdfOCProperties ocProperties = itextDoc.getCatalog().getOCProperties(false);
|
|
||||||
ArrayNode layersArray = objectMapper.createArrayNode();
|
|
||||||
if (ocProperties != null) {
|
|
||||||
|
|
||||||
for (PdfLayer layer : ocProperties.getLayers()) {
|
//TODO size
|
||||||
|
PDOptionalContentProperties ocProperties = pdfBoxDoc.getDocumentCatalog().getOCProperties();
|
||||||
|
ArrayNode layersArray = objectMapper.createArrayNode();
|
||||||
|
|
||||||
|
if (ocProperties != null) {
|
||||||
|
for (PDOptionalContentGroup ocg : ocProperties.getOptionalContentGroups()) {
|
||||||
ObjectNode layerNode = objectMapper.createObjectNode();
|
ObjectNode layerNode = objectMapper.createObjectNode();
|
||||||
layerNode.put("Name", layer.getPdfObject().getAsString(PdfName.Name).toString());
|
layerNode.put("Name", ocg.getName());
|
||||||
layersArray.add(layerNode);
|
layersArray.add(layerNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
other.set("Layers", layersArray);
|
other.set("Layers", layersArray);
|
||||||
|
|
||||||
//TODO Security
|
//TODO Security
|
||||||
|
@ -278,12 +275,6 @@ public class GetInfoOnPDF {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Digital Signatures using iText7 TODO
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PDStructureTreeRoot structureTreeRoot = pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot();
|
PDStructureTreeRoot structureTreeRoot = pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot();
|
||||||
ArrayNode structureTreeArray;
|
ArrayNode structureTreeArray;
|
||||||
try {
|
try {
|
||||||
|
@ -297,13 +288,13 @@ public class GetInfoOnPDF {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
boolean isPdfACompliant = checkOutputIntent(itextDoc, "PDF/A");
|
boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A");
|
||||||
boolean isPdfXCompliant = checkOutputIntent(itextDoc, "PDF/X");
|
boolean isPdfXCompliant = checkForStandard(pdfBoxDoc, "PDF/X");
|
||||||
boolean isPdfECompliant = checkForStandard(itextDoc, "PDF/E");
|
boolean isPdfECompliant = checkForStandard(pdfBoxDoc, "PDF/E");
|
||||||
boolean isPdfVTCompliant = checkForStandard(itextDoc, "PDF/VT");
|
boolean isPdfVTCompliant = checkForStandard(pdfBoxDoc, "PDF/VT");
|
||||||
boolean isPdfUACompliant = checkForStandard(itextDoc, "PDF/UA");
|
boolean isPdfUACompliant = checkForStandard(pdfBoxDoc, "PDF/UA");
|
||||||
boolean isPdfBCompliant = checkForStandard(itextDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard.
|
boolean isPdfBCompliant = checkForStandard(pdfBoxDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard.
|
||||||
boolean isPdfSECCompliant = checkForStandard(itextDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021.
|
boolean isPdfSECCompliant = checkForStandard(pdfBoxDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021.
|
||||||
|
|
||||||
compliancy.put("IsPDF/ACompliant", isPdfACompliant);
|
compliancy.put("IsPDF/ACompliant", isPdfACompliant);
|
||||||
compliancy.put("IsPDF/XCompliant", isPdfXCompliant);
|
compliancy.put("IsPDF/XCompliant", isPdfXCompliant);
|
||||||
|
@ -317,25 +308,37 @@ public class GetInfoOnPDF {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PDOutlineNode root = pdfBoxDoc.getDocumentCatalog().getDocumentOutline();
|
||||||
ArrayNode bookmarksArray = objectMapper.createArrayNode();
|
ArrayNode bookmarksArray = objectMapper.createArrayNode();
|
||||||
PdfOutline root = itextDoc.getOutlines(false);
|
|
||||||
if (root != null) {
|
if (root != null) {
|
||||||
for (PdfOutline child : root.getAllChildren()) {
|
for (PDOutlineItem child : root.children()) {
|
||||||
addOutlinesToArray(child, bookmarksArray);
|
addOutlinesToArray(child, bookmarksArray);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
other.set("Bookmarks/Outline/TOC", bookmarksArray);
|
other.set("Bookmarks/Outline/TOC", bookmarksArray);
|
||||||
|
|
||||||
byte[] xmpBytes = itextDoc.getXmpMetadata();
|
|
||||||
|
|
||||||
|
PDMetadata pdMetadata = pdfBoxDoc.getDocumentCatalog().getMetadata();
|
||||||
|
|
||||||
String xmpString = null;
|
String xmpString = null;
|
||||||
if (xmpBytes != null) {
|
|
||||||
|
if (pdMetadata != null) {
|
||||||
try {
|
try {
|
||||||
XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(xmpBytes);
|
COSInputStream is = pdMetadata.createInputStream();
|
||||||
xmpString = new String(XMPMetaFactory.serializeToBuffer(xmpMeta, new SerializeOptions()));
|
DomXmpParser domXmpParser = new DomXmpParser();
|
||||||
} catch (XMPException e) {
|
XMPMetadata xmpMeta = domXmpParser.parse(is);
|
||||||
|
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
new XmpSerializer().serialize(xmpMeta, os, true);
|
||||||
|
xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8);
|
||||||
|
} catch (XmpParsingException | IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
other.put("XMPMetadata", xmpString);
|
other.put("XMPMetadata", xmpString);
|
||||||
|
|
||||||
|
|
||||||
|
@ -347,8 +350,21 @@ public class GetInfoOnPDF {
|
||||||
PDEncryption pdfEncryption = pdfBoxDoc.getEncryption();
|
PDEncryption pdfEncryption = pdfBoxDoc.getEncryption();
|
||||||
encryption.put("EncryptionAlgorithm", pdfEncryption.getFilter());
|
encryption.put("EncryptionAlgorithm", pdfEncryption.getFilter());
|
||||||
encryption.put("KeyLength", pdfEncryption.getLength());
|
encryption.put("KeyLength", pdfEncryption.getLength());
|
||||||
encryption.put("Permissions", pdfBoxDoc.getCurrentAccessPermission().toString());
|
AccessPermission ap = pdfBoxDoc.getCurrentAccessPermission();
|
||||||
|
if (ap != null) {
|
||||||
|
ObjectNode permissionsNode = objectMapper.createObjectNode();
|
||||||
|
|
||||||
|
permissionsNode.put("CanAssembleDocument", ap.canAssembleDocument());
|
||||||
|
permissionsNode.put("CanExtractContent", ap.canExtractContent());
|
||||||
|
permissionsNode.put("CanExtractForAccessibility", ap.canExtractForAccessibility());
|
||||||
|
permissionsNode.put("CanFillInForm", ap.canFillInForm());
|
||||||
|
permissionsNode.put("CanModify", ap.canModify());
|
||||||
|
permissionsNode.put("CanModifyAnnotations", ap.canModifyAnnotations());
|
||||||
|
permissionsNode.put("CanPrint", ap.canPrint());
|
||||||
|
permissionsNode.put("CanPrintDegraded", ap.canPrintDegraded());
|
||||||
|
|
||||||
|
encryption.set("Permissions", permissionsNode); // set the node under "Permissions"
|
||||||
|
}
|
||||||
// Add other encryption-related properties as needed
|
// Add other encryption-related properties as needed
|
||||||
} else {
|
} else {
|
||||||
encryption.put("IsEncrypted", false);
|
encryption.put("IsEncrypted", false);
|
||||||
|
@ -358,43 +374,65 @@ public class GetInfoOnPDF {
|
||||||
|
|
||||||
|
|
||||||
ObjectNode pageInfoParent = objectMapper.createObjectNode();
|
ObjectNode pageInfoParent = objectMapper.createObjectNode();
|
||||||
for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) {
|
for (int pageNum = 0; pageNum < pdfBoxDoc.getNumberOfPages(); pageNum++) {
|
||||||
ObjectNode pageInfo = objectMapper.createObjectNode();
|
ObjectNode pageInfo = objectMapper.createObjectNode();
|
||||||
|
|
||||||
|
// Retrieve the page
|
||||||
|
PDPage page = pdfBoxDoc.getPage(pageNum);
|
||||||
|
|
||||||
// Page-level Information
|
// Page-level Information
|
||||||
Rectangle pageSize = itextDoc.getPage(pageNum).getPageSize();
|
PDRectangle mediaBox = page.getMediaBox();
|
||||||
pageInfo.put("Width", pageSize.getWidth());
|
|
||||||
pageInfo.put("Height", pageSize.getHeight());
|
float width = mediaBox.getWidth();
|
||||||
pageInfo.put("Rotation", itextDoc.getPage(pageNum).getRotation());
|
float height = mediaBox.getHeight();
|
||||||
pageInfo.put("Page Orientation", getPageOrientation(pageSize.getWidth(),pageSize.getHeight()));
|
|
||||||
pageInfo.put("Standard Size", getPageSize(pageSize.getWidth(),pageSize.getHeight()));
|
ObjectNode sizeInfo = objectMapper.createObjectNode();
|
||||||
|
|
||||||
|
getDimensionInfo(sizeInfo, width, height);
|
||||||
|
|
||||||
|
sizeInfo.put("Standard Page", getPageSize(width, height));
|
||||||
|
pageInfo.set("Size", sizeInfo);
|
||||||
|
|
||||||
|
pageInfo.put("Rotation", page.getRotation());
|
||||||
|
pageInfo.put("Page Orientation", getPageOrientation(width, height));
|
||||||
|
|
||||||
|
|
||||||
// Boxes
|
// Boxes
|
||||||
pageInfo.put("MediaBox", itextDoc.getPage(pageNum).getMediaBox().toString());
|
pageInfo.put("MediaBox", mediaBox.toString());
|
||||||
pageInfo.put("CropBox", itextDoc.getPage(pageNum).getCropBox().toString());
|
|
||||||
pageInfo.put("BleedBox", itextDoc.getPage(pageNum).getBleedBox().toString());
|
// Assuming the following boxes are defined for your document; if not, you may get null values.
|
||||||
pageInfo.put("TrimBox", itextDoc.getPage(pageNum).getTrimBox().toString());
|
PDRectangle cropBox = page.getCropBox();
|
||||||
pageInfo.put("ArtBox", itextDoc.getPage(pageNum).getArtBox().toString());
|
pageInfo.put("CropBox", cropBox == null ? "Undefined" : cropBox.toString());
|
||||||
|
|
||||||
|
PDRectangle bleedBox = page.getBleedBox();
|
||||||
|
pageInfo.put("BleedBox", bleedBox == null ? "Undefined" : bleedBox.toString());
|
||||||
|
|
||||||
|
PDRectangle trimBox = page.getTrimBox();
|
||||||
|
pageInfo.put("TrimBox", trimBox == null ? "Undefined" : trimBox.toString());
|
||||||
|
|
||||||
|
PDRectangle artBox = page.getArtBox();
|
||||||
|
pageInfo.put("ArtBox", artBox == null ? "Undefined" : artBox.toString());
|
||||||
|
|
||||||
// Content Extraction
|
// Content Extraction
|
||||||
PDFTextStripper textStripper = new PDFTextStripper();
|
PDFTextStripper textStripper = new PDFTextStripper();
|
||||||
textStripper.setStartPage(pageNum -1);
|
textStripper.setStartPage(pageNum + 1);
|
||||||
textStripper.setEndPage(pageNum - 1);
|
textStripper.setEndPage(pageNum +1);
|
||||||
String pageText = textStripper.getText(pdfBoxDoc);
|
String pageText = textStripper.getText(pdfBoxDoc);
|
||||||
|
|
||||||
pageInfo.put("Text Characters Count", pageText.length()); //
|
pageInfo.put("Text Characters Count", pageText.length()); //
|
||||||
|
|
||||||
// Annotations
|
// Annotations
|
||||||
List<PdfAnnotation> annotations = itextDoc.getPage(pageNum).getAnnotations();
|
|
||||||
|
List<PDAnnotation> annotations = page.getAnnotations();
|
||||||
|
|
||||||
int subtypeCount = 0;
|
int subtypeCount = 0;
|
||||||
int contentsCount = 0;
|
int contentsCount = 0;
|
||||||
|
|
||||||
for (PdfAnnotation annotation : annotations) {
|
for (PDAnnotation annotation : annotations) {
|
||||||
if(annotation.getSubtype() != null) {
|
if (annotation.getSubtype() != null) {
|
||||||
subtypeCount++; // Increase subtype count
|
subtypeCount++; // Increase subtype count
|
||||||
}
|
}
|
||||||
if(annotation.getContents() != null) {
|
if (annotation.getContents() != null) {
|
||||||
contentsCount++; // Increase contents count
|
contentsCount++; // Increase contents count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,27 +443,33 @@ public class GetInfoOnPDF {
|
||||||
annotationsObject.put("ContentsCount", contentsCount);
|
annotationsObject.put("ContentsCount", contentsCount);
|
||||||
pageInfo.set("Annotations", annotationsObject);
|
pageInfo.set("Annotations", annotationsObject);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Images (simplified)
|
// Images (simplified)
|
||||||
// This part is non-trivial as images can be embedded in multiple ways in a PDF.
|
// This part is non-trivial as images can be embedded in multiple ways in a PDF.
|
||||||
// Here is a basic structure to recognize image XObjects on a page.
|
// Here is a basic structure to recognize image XObjects on a page.
|
||||||
ArrayNode imagesArray = objectMapper.createArrayNode();
|
ArrayNode imagesArray = objectMapper.createArrayNode();
|
||||||
PdfResources resources = itextDoc.getPage(pageNum).getResources();
|
PDResources resources = page.getResources();
|
||||||
for (PdfName name : resources.getResourceNames()) {
|
|
||||||
PdfObject obj = resources.getResource(name);
|
|
||||||
if (obj instanceof PdfStream) {
|
for (COSName name : resources.getXObjectNames()) {
|
||||||
PdfStream stream = (PdfStream) obj;
|
PDXObject xObject = resources.getXObject(name);
|
||||||
if (PdfName.Image.equals(stream.getAsName(PdfName.Subtype))) {
|
if (xObject instanceof PDImageXObject) {
|
||||||
|
PDImageXObject image = (PDImageXObject) xObject;
|
||||||
|
|
||||||
ObjectNode imageNode = objectMapper.createObjectNode();
|
ObjectNode imageNode = objectMapper.createObjectNode();
|
||||||
imageNode.put("Width", stream.getAsNumber(PdfName.Width).intValue());
|
imageNode.put("Width", image.getWidth());
|
||||||
imageNode.put("Height", stream.getAsNumber(PdfName.Height).intValue());
|
imageNode.put("Height", image.getHeight());
|
||||||
PdfObject colorSpace = stream.get(PdfName.ColorSpace);
|
if(image.getMetadata() != null && image.getMetadata().getFile() != null && image.getMetadata().getFile().getFile() != null) {
|
||||||
if (colorSpace != null) {
|
imageNode.put("Name", image.getMetadata().getFile().getFile());
|
||||||
imageNode.put("ColorSpace", colorSpace.toString());
|
|
||||||
}
|
}
|
||||||
|
if (image.getColorSpace() != null) {
|
||||||
|
imageNode.put("ColorSpace", image.getColorSpace().getName());
|
||||||
|
}
|
||||||
|
|
||||||
imagesArray.add(imageNode);
|
imagesArray.add(imageNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
pageInfo.set("Images", imagesArray);
|
pageInfo.set("Images", imagesArray);
|
||||||
|
|
||||||
|
|
||||||
|
@ -433,11 +477,12 @@ public class GetInfoOnPDF {
|
||||||
ArrayNode linksArray = objectMapper.createArrayNode();
|
ArrayNode linksArray = objectMapper.createArrayNode();
|
||||||
Set<String> uniqueURIs = new HashSet<>(); // To store unique URIs
|
Set<String> uniqueURIs = new HashSet<>(); // To store unique URIs
|
||||||
|
|
||||||
for (PdfAnnotation annotation : annotations) {
|
for (PDAnnotation annotation : annotations) {
|
||||||
if (annotation instanceof PdfLinkAnnotation) {
|
if (annotation instanceof PDAnnotationLink) {
|
||||||
PdfLinkAnnotation linkAnnotation = (PdfLinkAnnotation) annotation;
|
PDAnnotationLink linkAnnotation = (PDAnnotationLink) annotation;
|
||||||
if(linkAnnotation != null && linkAnnotation.getAction() != null) {
|
if (linkAnnotation.getAction() instanceof PDActionURI) {
|
||||||
String uri = linkAnnotation.getAction().toString();
|
PDActionURI uriAction = (PDActionURI) linkAnnotation.getAction();
|
||||||
|
String uri = uriAction.getURI();
|
||||||
uniqueURIs.add(uri); // Add to set to ensure uniqueness
|
uniqueURIs.add(uri); // Add to set to ensure uniqueness
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,83 +496,41 @@ public class GetInfoOnPDF {
|
||||||
}
|
}
|
||||||
pageInfo.set("Links", linksArray);
|
pageInfo.set("Links", linksArray);
|
||||||
|
|
||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
ArrayNode fontsArray = objectMapper.createArrayNode();
|
ArrayNode fontsArray = objectMapper.createArrayNode();
|
||||||
PdfDictionary fontDicts = resources.getResource(PdfName.Font);
|
|
||||||
Set<String> uniqueSubtypes = new HashSet<>(); // To store unique subtypes
|
|
||||||
|
|
||||||
// Map to store unique fonts and their counts
|
|
||||||
Map<String, ObjectNode> uniqueFontsMap = new HashMap<>();
|
Map<String, ObjectNode> uniqueFontsMap = new HashMap<>();
|
||||||
|
|
||||||
if (fontDicts != null) {
|
for (COSName fontName : resources.getFontNames()) {
|
||||||
for (PdfName key : fontDicts.keySet()) {
|
PDFont font = resources.getFont(fontName);
|
||||||
ObjectNode fontNode = objectMapper.createObjectNode(); // Create a new font node for each font
|
ObjectNode fontNode = objectMapper.createObjectNode();
|
||||||
PdfDictionary font = fontDicts.getAsDictionary(key);
|
|
||||||
|
|
||||||
boolean isEmbedded = font.containsKey(PdfName.FontFile) ||
|
fontNode.put("IsEmbedded", font.isEmbedded());
|
||||||
font.containsKey(PdfName.FontFile2) ||
|
|
||||||
font.containsKey(PdfName.FontFile3);
|
|
||||||
fontNode.put("IsEmbedded", isEmbedded);
|
|
||||||
|
|
||||||
if (font.containsKey(PdfName.Encoding)) {
|
// PDFBox provides Font's BaseFont (i.e., the font name) directly
|
||||||
String encoding = font.getAsName(PdfName.Encoding).toString();
|
fontNode.put("Name", font.getName());
|
||||||
fontNode.put("Encoding", encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font.getAsString(PdfName.BaseFont) != null) {
|
fontNode.put("Subtype", font.getType());
|
||||||
fontNode.put("Name", font.getAsString(PdfName.BaseFont).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
String subtype = null;
|
PDFontDescriptor fontDescriptor = font.getFontDescriptor();
|
||||||
if (font.containsKey(PdfName.Subtype)) {
|
|
||||||
subtype = font.getAsName(PdfName.Subtype).toString();
|
|
||||||
uniqueSubtypes.add(subtype); // Add to set to ensure uniqueness
|
|
||||||
}
|
|
||||||
fontNode.put("Subtype", subtype);
|
|
||||||
|
|
||||||
PdfDictionary fontDescriptor = font.getAsDictionary(PdfName.FontDescriptor);
|
|
||||||
if (fontDescriptor != null) {
|
if (fontDescriptor != null) {
|
||||||
if (fontDescriptor.containsKey(PdfName.ItalicAngle)) {
|
fontNode.put("ItalicAngle", fontDescriptor.getItalicAngle());
|
||||||
fontNode.put("ItalicAngle", fontDescriptor.getAsNumber(PdfName.ItalicAngle).floatValue());
|
int flags = fontDescriptor.getFlags();
|
||||||
|
fontNode.put("IsItalic", (flags & 1) != 0);
|
||||||
|
fontNode.put("IsBold", (flags & 64) != 0);
|
||||||
|
fontNode.put("IsFixedPitch", (flags & 2) != 0);
|
||||||
|
fontNode.put("IsSerif", (flags & 4) != 0);
|
||||||
|
fontNode.put("IsSymbolic", (flags & 8) != 0);
|
||||||
|
fontNode.put("IsScript", (flags & 16) != 0);
|
||||||
|
fontNode.put("IsNonsymbolic", (flags & 32) != 0);
|
||||||
|
|
||||||
|
fontNode.put("FontFamily", fontDescriptor.getFontFamily());
|
||||||
|
// Font stretch and BBox are not directly available in PDFBox's API, so these are omitted for simplicity
|
||||||
|
fontNode.put("FontWeight", fontDescriptor.getFontWeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fontDescriptor.containsKey(PdfName.Flags)) {
|
|
||||||
int flags = fontDescriptor.getAsNumber(PdfName.Flags).intValue();
|
|
||||||
fontNode.put("IsItalic", (flags & 64) != 0);
|
|
||||||
fontNode.put("IsBold", (flags & 1 << 16) != 0);
|
|
||||||
fontNode.put("IsFixedPitch", (flags & 1) != 0);
|
|
||||||
fontNode.put("IsSerif", (flags & 2) != 0);
|
|
||||||
fontNode.put("IsSymbolic", (flags & 4) != 0);
|
|
||||||
fontNode.put("IsScript", (flags & 8) != 0);
|
|
||||||
fontNode.put("IsNonsymbolic", (flags & 16) != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fontDescriptor.containsKey(PdfName.FontFamily)) {
|
|
||||||
String fontFamily = fontDescriptor.getAsString(PdfName.FontFamily).toString();
|
|
||||||
fontNode.put("FontFamily", fontFamily);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fontDescriptor.containsKey(PdfName.FontStretch)) {
|
|
||||||
String fontStretch = fontDescriptor.getAsName(PdfName.FontStretch).toString();
|
|
||||||
fontNode.put("FontStretch", fontStretch);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fontDescriptor.containsKey(PdfName.FontBBox)) {
|
|
||||||
PdfArray bbox = fontDescriptor.getAsArray(PdfName.FontBBox);
|
|
||||||
fontNode.put("FontBoundingBox", bbox.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fontDescriptor.containsKey(PdfName.FontWeight)) {
|
|
||||||
float fontWeight = fontDescriptor.getAsNumber(PdfName.FontWeight).floatValue();
|
|
||||||
fontNode.put("FontWeight", fontWeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font.containsKey(PdfName.ToUnicode)) {
|
|
||||||
fontNode.put("HasToUnicodeMap", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fontNode.size() > 0) {
|
|
||||||
// Create a unique key for this font node based on its attributes
|
// Create a unique key for this font node based on its attributes
|
||||||
String uniqueKey = fontNode.toString();
|
String uniqueKey = fontNode.toString();
|
||||||
|
|
||||||
|
@ -541,8 +544,6 @@ public class GetInfoOnPDF {
|
||||||
uniqueFontsMap.put(uniqueKey, fontNode);
|
uniqueFontsMap.put(uniqueKey, fontNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add unique font entries to fontsArray
|
// Add unique font entries to fontsArray
|
||||||
for (ObjectNode uniqueFontNode : uniqueFontsMap.values()) {
|
for (ObjectNode uniqueFontNode : uniqueFontsMap.values()) {
|
||||||
|
@ -554,42 +555,50 @@ public class GetInfoOnPDF {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Access resources dictionary
|
|
||||||
PdfDictionary resourcesDict = itextDoc.getPage(pageNum).getResources().getPdfObject();
|
|
||||||
|
|
||||||
// Color Spaces & ICC Profiles
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Access resources dictionary
|
||||||
ArrayNode colorSpacesArray = objectMapper.createArrayNode();
|
ArrayNode colorSpacesArray = objectMapper.createArrayNode();
|
||||||
PdfDictionary colorSpaces = resourcesDict.getAsDictionary(PdfName.ColorSpace);
|
|
||||||
if (colorSpaces != null) {
|
Iterable<COSName> colorSpaceNames = resources.getColorSpaceNames();
|
||||||
for (PdfName name : colorSpaces.keySet()) {
|
for (COSName name : colorSpaceNames) {
|
||||||
PdfObject colorSpaceObject = colorSpaces.get(name);
|
PDColorSpace colorSpace = resources.getColorSpace(name);
|
||||||
if (colorSpaceObject instanceof PdfArray) {
|
if (colorSpace instanceof PDICCBased) {
|
||||||
PdfArray colorSpaceArray = (PdfArray) colorSpaceObject;
|
PDICCBased iccBased = (PDICCBased) colorSpace;
|
||||||
if (colorSpaceArray.size() > 1 && colorSpaceArray.get(0) instanceof PdfName && PdfName.ICCBased.equals(colorSpaceArray.get(0))) {
|
PDStream iccData = iccBased.getPDStream();
|
||||||
ObjectNode iccProfileNode = objectMapper.createObjectNode();
|
byte[] iccBytes = iccData.toByteArray();
|
||||||
PdfStream iccStream = (PdfStream) colorSpaceArray.get(1);
|
|
||||||
byte[] iccData = iccStream.getBytes();
|
|
||||||
// TODO: Further decode and analyze the ICC data if needed
|
// TODO: Further decode and analyze the ICC data if needed
|
||||||
iccProfileNode.put("ICC Profile Length", iccData.length);
|
ObjectNode iccProfileNode = objectMapper.createObjectNode();
|
||||||
|
iccProfileNode.put("ICC Profile Length", iccBytes.length);
|
||||||
colorSpacesArray.add(iccProfileNode);
|
colorSpacesArray.add(iccProfileNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray);
|
pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray);
|
||||||
|
|
||||||
|
|
||||||
// Other XObjects
|
// Other XObjects
|
||||||
Map<String, Integer> xObjectCountMap = new HashMap<>(); // To store the count for each type
|
Map<String, Integer> xObjectCountMap = new HashMap<>(); // To store the count for each type
|
||||||
PdfDictionary xObjects = resourcesDict.getAsDictionary(PdfName.XObject);
|
for (COSName name : resources.getXObjectNames()) {
|
||||||
if (xObjects != null) {
|
PDXObject xObject = resources.getXObject(name);
|
||||||
for (PdfName name : xObjects.keySet()) {
|
String xObjectType;
|
||||||
PdfStream xObjectStream = xObjects.getAsStream(name);
|
|
||||||
String xObjectType = xObjectStream.getAsName(PdfName.Subtype).toString();
|
if (xObject instanceof PDImageXObject) {
|
||||||
|
xObjectType = "Image";
|
||||||
|
} else if (xObject instanceof PDFormXObject) {
|
||||||
|
xObjectType = "Form";
|
||||||
|
} else {
|
||||||
|
xObjectType = "Other";
|
||||||
|
}
|
||||||
|
|
||||||
// Increment the count for this type in the map
|
// Increment the count for this type in the map
|
||||||
xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1);
|
xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Add the count map to pageInfo (or wherever you want to store it)
|
// Add the count map to pageInfo (or wherever you want to store it)
|
||||||
ObjectNode xObjectCountNode = objectMapper.createObjectNode();
|
ObjectNode xObjectCountNode = objectMapper.createObjectNode();
|
||||||
|
@ -600,19 +609,22 @@ public class GetInfoOnPDF {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ArrayNode multimediaArray = objectMapper.createArrayNode();
|
ArrayNode multimediaArray = objectMapper.createArrayNode();
|
||||||
for (PdfAnnotation annotation : annotations) {
|
|
||||||
if (PdfName.RichMedia.equals(annotation.getSubtype())) {
|
for (PDAnnotation annotation : annotations) {
|
||||||
|
if ("RichMedia".equals(annotation.getSubtype())) {
|
||||||
ObjectNode multimediaNode = objectMapper.createObjectNode();
|
ObjectNode multimediaNode = objectMapper.createObjectNode();
|
||||||
// Extract details from the dictionary as needed
|
// Extract details from the annotation as needed
|
||||||
multimediaArray.add(multimediaNode);
|
multimediaArray.add(multimediaNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInfo.set("Multimedia", multimediaArray);
|
pageInfo.set("Multimedia", multimediaArray);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pageInfoParent.set("Page " + pageNum, pageInfo);
|
pageInfoParent.set("Page " + (pageNum+1), pageInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -638,17 +650,21 @@ public class GetInfoOnPDF {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addOutlinesToArray(PdfOutline outline, ArrayNode arrayNode) {
|
private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) {
|
||||||
if (outline == null) return;
|
if (outline == null) return;
|
||||||
|
|
||||||
ObjectNode outlineNode = objectMapper.createObjectNode();
|
ObjectNode outlineNode = objectMapper.createObjectNode();
|
||||||
outlineNode.put("Title", outline.getTitle());
|
outlineNode.put("Title", outline.getTitle());
|
||||||
// You can add other properties if needed
|
// You can add other properties if needed
|
||||||
arrayNode.add(outlineNode);
|
arrayNode.add(outlineNode);
|
||||||
|
|
||||||
for (PdfOutline child : outline.getAllChildren()) {
|
PDOutlineItem child = outline.getFirstChild();
|
||||||
|
while (child != null) {
|
||||||
addOutlinesToArray(child, arrayNode);
|
addOutlinesToArray(child, arrayNode);
|
||||||
|
child = child.getNextSibling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPageOrientation(double width, double height) {
|
public String getPageOrientation(double width, double height) {
|
||||||
if (width > height) {
|
if (width > height) {
|
||||||
return "Landscape";
|
return "Landscape";
|
||||||
|
@ -658,67 +674,79 @@ public class GetInfoOnPDF {
|
||||||
return "Square";
|
return "Square";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public String getPageSize(double width, double height) {
|
public String getPageSize(float width, float height) {
|
||||||
// Common aspect ratios used for standard paper sizes
|
// Define standard page sizes
|
||||||
double[] aspectRatios = {4.0 / 3.0, 3.0 / 2.0, Math.sqrt(2.0), 16.0 / 9.0};
|
Map<String, PDRectangle> standardSizes = new HashMap<>();
|
||||||
|
standardSizes.put("Letter", PDRectangle.LETTER);
|
||||||
|
standardSizes.put("LEGAL", PDRectangle.LEGAL);
|
||||||
|
standardSizes.put("A0", PDRectangle.A0);
|
||||||
|
standardSizes.put("A1", PDRectangle.A1);
|
||||||
|
standardSizes.put("A2", PDRectangle.A2);
|
||||||
|
standardSizes.put("A3", PDRectangle.A3);
|
||||||
|
standardSizes.put("A4", PDRectangle.A4);
|
||||||
|
standardSizes.put("A5", PDRectangle.A5);
|
||||||
|
standardSizes.put("A6", PDRectangle.A6);
|
||||||
|
|
||||||
// Check if the page matches any common aspect ratio
|
for (Map.Entry<String, PDRectangle> entry : standardSizes.entrySet()) {
|
||||||
for (double aspectRatio : aspectRatios) {
|
PDRectangle size = entry.getValue();
|
||||||
if (isCloseToAspectRatio(width, height, aspectRatio)) {
|
if (isCloseToSize(width, height, size.getWidth(), size.getHeight())) {
|
||||||
return "Standard";
|
return entry.getKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not a standard aspect ratio, consider it as a custom size
|
|
||||||
return "Custom";
|
return "Custom";
|
||||||
}
|
}
|
||||||
private boolean isCloseToAspectRatio(double width, double height, double aspectRatio) {
|
|
||||||
// Calculate the aspect ratio of the page
|
|
||||||
double pageAspectRatio = width / height;
|
|
||||||
|
|
||||||
// Compare the page aspect ratio with the common aspect ratio within a threshold
|
private boolean isCloseToSize(float width, float height, float standardWidth, float standardHeight) {
|
||||||
return Math.abs(pageAspectRatio - aspectRatio) <= 0.05;
|
float tolerance = 1.0f; // You can adjust the tolerance as needed
|
||||||
|
return Math.abs(width - standardWidth) <= tolerance && Math.abs(height - standardHeight) <= tolerance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean checkForStandard(PdfDocument document, String standardKeyword) {
|
|
||||||
// Check Output Intents
|
|
||||||
boolean foundInOutputIntents = checkOutputIntent(document, standardKeyword);
|
|
||||||
if (foundInOutputIntents) return true;
|
|
||||||
|
|
||||||
// Check XMP Metadata (rudimentary)
|
public ObjectNode getDimensionInfo(ObjectNode dimensionInfo, float width, float height) {
|
||||||
|
float ppi = 72; // Points Per Inch
|
||||||
|
|
||||||
|
float widthInInches = width / ppi;
|
||||||
|
float heightInInches = height / ppi;
|
||||||
|
|
||||||
|
float widthInCm = widthInInches * 2.54f;
|
||||||
|
float heightInCm = heightInInches * 2.54f;
|
||||||
|
|
||||||
|
dimensionInfo.put("Width (px)", String.format("%.2f", width));
|
||||||
|
dimensionInfo.put("Height (px)", String.format("%.2f", height));
|
||||||
|
dimensionInfo.put("Width (in)", String.format("%.2f", widthInInches));
|
||||||
|
dimensionInfo.put("Height (in)", String.format("%.2f", heightInInches));
|
||||||
|
dimensionInfo.put("Width (cm)", String.format("%.2f", widthInCm));
|
||||||
|
dimensionInfo.put("Height (cm)", String.format("%.2f", heightInCm));
|
||||||
|
return dimensionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean checkForStandard(PDDocument document, String standardKeyword) {
|
||||||
|
// Check XMP Metadata
|
||||||
try {
|
try {
|
||||||
byte[] metadataBytes = document.getXmpMetadata();
|
PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();
|
||||||
if (metadataBytes != null) {
|
if (pdMetadata != null) {
|
||||||
XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(metadataBytes);
|
COSInputStream metaStream = pdMetadata.createInputStream();
|
||||||
String xmpString = xmpMeta.dumpObject();
|
DomXmpParser domXmpParser = new DomXmpParser();
|
||||||
|
XMPMetadata xmpMeta = domXmpParser.parse(metaStream);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
new XmpSerializer().serialize(xmpMeta, baos, true);
|
||||||
|
String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
if (xmpString.contains(standardKeyword)) {
|
if (xmpString.contains(standardKeyword)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (XMPException e) {
|
} catch (Exception e) { // Catching general exception for brevity, ideally you'd catch specific exceptions.
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean checkOutputIntent(PdfDocument document, String standard) {
|
|
||||||
PdfArray outputIntents = document.getCatalog().getPdfObject().getAsArray(PdfName.OutputIntents);
|
|
||||||
if (outputIntents != null && !outputIntents.isEmpty()) {
|
|
||||||
for (int i = 0; i < outputIntents.size(); i++) {
|
|
||||||
PdfDictionary outputIntentDict = outputIntents.getAsDictionary(i);
|
|
||||||
if (outputIntentDict != null) {
|
|
||||||
PdfString s = outputIntentDict.getAsString(PdfName.S);
|
|
||||||
if (s != null && s.toString().contains(standard)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayNode exploreStructureTree(List<Object> nodes) {
|
public ArrayNode exploreStructureTree(List<Object> nodes) {
|
||||||
ArrayNode elementsArray = objectMapper.createArrayNode();
|
ArrayNode elementsArray = objectMapper.createArrayNode();
|
||||||
if (nodes != null) {
|
if (nodes != null) {
|
||||||
|
@ -773,7 +801,7 @@ public class GetInfoOnPDF {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPageModeDescription(PdfName pageMode) {
|
private String getPageModeDescription(String pageMode) {
|
||||||
return pageMode != null ? pageMode.toString().replaceFirst("/", "") : "Unknown";
|
return pageMode != null ? pageMode.toString().replaceFirst("/", "") : "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,19 @@ import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.security.AddPasswordRequest;
|
||||||
|
import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/security")
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class PasswordController {
|
public class PasswordController {
|
||||||
|
|
||||||
|
@ -31,13 +32,12 @@ public class PasswordController {
|
||||||
summary = "Remove password from a PDF file",
|
summary = "Remove password from a PDF file",
|
||||||
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO"
|
description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> removePassword(
|
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile fileInput = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file from which the password should be removed", required = true)
|
String password = request.getPassword();
|
||||||
MultipartFile fileInput,
|
|
||||||
@RequestParam(name = "password")
|
|
||||||
@Parameter(description = "The password of the PDF file", required = true)
|
|
||||||
String password) throws IOException {
|
|
||||||
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
|
PDDocument document = PDDocument.load(fileInput.getBytes(), password);
|
||||||
document.setAllSecurityToBeRemoved(true);
|
document.setAllSecurityToBeRemoved(true);
|
||||||
return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf");
|
return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_password_removed.pdf");
|
||||||
|
@ -48,44 +48,19 @@ public class PasswordController {
|
||||||
summary = "Add password to a PDF file",
|
summary = "Add password to a PDF file",
|
||||||
description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF"
|
description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF"
|
||||||
)
|
)
|
||||||
public ResponseEntity<byte[]> addPassword(
|
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile fileInput = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file to which the password should be added", required = true)
|
String ownerPassword = request.getOwnerPassword();
|
||||||
MultipartFile fileInput,
|
String password = request.getPassword();
|
||||||
@RequestParam(value = "", name = "ownerPassword")
|
int keyLength = request.getKeyLength();
|
||||||
@Parameter(description = "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)")
|
boolean canAssembleDocument = request.isCanAssembleDocument();
|
||||||
String ownerPassword,
|
boolean canExtractContent = request.isCanExtractContent();
|
||||||
@RequestParam( name = "password", required = false)
|
boolean canExtractForAccessibility = request.isCanExtractForAccessibility();
|
||||||
@Parameter(description = "The password to be added to the PDF file (Restricts the opening of the document itself.)")
|
boolean canFillInForm = request.isCanFillInForm();
|
||||||
String password,
|
boolean canModify = request.isCanModify();
|
||||||
@RequestParam( name = "keyLength", required = false)
|
boolean canModifyAnnotations = request.isCanModifyAnnotations();
|
||||||
@Parameter(description = "The length of the encryption key", schema = @Schema(allowableValues = {"40", "128", "256"}))
|
boolean canPrint = request.isCanPrint();
|
||||||
int keyLength,
|
boolean canPrintFaithful = request.isCanPrintFaithful();
|
||||||
@RequestParam( name = "canAssembleDocument", required = false)
|
|
||||||
@Parameter(description = "Whether the document assembly is allowed", example = "false")
|
|
||||||
boolean canAssembleDocument,
|
|
||||||
@RequestParam( name = "canExtractContent", required = false)
|
|
||||||
@Parameter(description = "Whether content extraction for accessibility is allowed", example = "false")
|
|
||||||
boolean canExtractContent,
|
|
||||||
@RequestParam( name = "canExtractForAccessibility", required = false)
|
|
||||||
@Parameter(description = "Whether content extraction for accessibility is allowed", example = "false")
|
|
||||||
boolean canExtractForAccessibility,
|
|
||||||
@RequestParam( name = "canFillInForm", required = false)
|
|
||||||
@Parameter(description = "Whether form filling is allowed", example = "false")
|
|
||||||
boolean canFillInForm,
|
|
||||||
@RequestParam( name = "canModify", required = false)
|
|
||||||
@Parameter(description = "Whether the document modification is allowed", example = "false")
|
|
||||||
boolean canModify,
|
|
||||||
@RequestParam( name = "canModifyAnnotations", required = false)
|
|
||||||
@Parameter(description = "Whether modification of annotations is allowed", example = "false")
|
|
||||||
boolean canModifyAnnotations,
|
|
||||||
@RequestParam(name = "canPrint", required = false)
|
|
||||||
@Parameter(description = "Whether printing of the document is allowed", example = "false")
|
|
||||||
boolean canPrint,
|
|
||||||
@RequestParam( name = "canPrintFaithful", required = false)
|
|
||||||
@Parameter(description = "Whether faithful printing is allowed", example = "false")
|
|
||||||
boolean canPrintFaithful
|
|
||||||
) throws IOException {
|
|
||||||
|
|
||||||
PDDocument document = PDDocument.load(fileInput.getBytes());
|
PDDocument document = PDDocument.load(fileInput.getBytes());
|
||||||
AccessPermission ap = new AccessPermission();
|
AccessPermission ap = new AccessPermission();
|
||||||
|
@ -99,14 +74,14 @@ public class PasswordController {
|
||||||
ap.setCanPrintFaithful(!canPrintFaithful);
|
ap.setCanPrintFaithful(!canPrintFaithful);
|
||||||
StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPassword, password, ap);
|
StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPassword, password, ap);
|
||||||
|
|
||||||
|
if(!"".equals(ownerPassword) || !"".equals(password)) {
|
||||||
|
|
||||||
spp.setEncryptionKeyLength(keyLength);
|
spp.setEncryptionKeyLength(keyLength);
|
||||||
|
}
|
||||||
spp.setPermissions(ap);
|
spp.setPermissions(ap);
|
||||||
|
|
||||||
document.protect(spp);
|
document.protect(spp);
|
||||||
|
|
||||||
|
if("".equals(ownerPassword) && "".equals(password))
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_permissions.pdf");
|
||||||
return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf");
|
return WebResponseUtils.pdfDocToWebResponse(document, fileInput.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_passworded.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.graphics.image.LosslessFactory;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.apache.pdfbox.rendering.ImageType;
|
||||||
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.PDFText;
|
||||||
|
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
|
||||||
|
import stirling.software.SPDF.pdf.TextFinder;
|
||||||
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/security")
|
||||||
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
|
public class RedactController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RedactController.class);
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping(value = "/auto-redact", consumes = "multipart/form-data")
|
||||||
|
@Operation(summary = "Redacts listOfText in a PDF document",
|
||||||
|
description = "This operation takes an input PDF file and redacts the provided listOfText. Input:PDF, Output:PDF, Type:SISO")
|
||||||
|
public ResponseEntity<byte[]> redactPdf(@ModelAttribute RedactPdfRequest request) throws Exception {
|
||||||
|
MultipartFile file = request.getFileInput();
|
||||||
|
String listOfTextString = request.getListOfText();
|
||||||
|
boolean useRegex = request.isUseRegex();
|
||||||
|
boolean wholeWordSearchBool = request.isWholeWordSearch();
|
||||||
|
String colorString = request.getRedactColor();
|
||||||
|
float customPadding = request.getCustomPadding();
|
||||||
|
boolean convertPDFToImage = request.isConvertPDFToImage();
|
||||||
|
|
||||||
|
System.out.println(listOfTextString);
|
||||||
|
String[] listOfText = listOfTextString.split("\n");
|
||||||
|
byte[] bytes = file.getBytes();
|
||||||
|
PDDocument document = PDDocument.load(new ByteArrayInputStream(bytes));
|
||||||
|
|
||||||
|
Color redactColor;
|
||||||
|
try {
|
||||||
|
if (!colorString.startsWith("#")) {
|
||||||
|
colorString = "#" + colorString;
|
||||||
|
}
|
||||||
|
redactColor = Color.decode(colorString);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.warn("Invalid color string provided. Using default color BLACK for redaction.");
|
||||||
|
redactColor = Color.BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (String text : listOfText) {
|
||||||
|
text = text.trim();
|
||||||
|
System.out.println(text);
|
||||||
|
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
||||||
|
List<PDFText> foundTexts = textFinder.getTextLocations(document);
|
||||||
|
redactFoundText(document, foundTexts, customPadding,redactColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (convertPDFToImage) {
|
||||||
|
PDDocument imageDocument = new PDDocument();
|
||||||
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
|
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||||
|
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
|
||||||
|
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
|
||||||
|
imageDocument.addPage(newPage);
|
||||||
|
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(imageDocument, newPage);
|
||||||
|
contentStream.drawImage(pdImage, 0, 0);
|
||||||
|
contentStream.close();
|
||||||
|
}
|
||||||
|
document.close();
|
||||||
|
document = imageDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
document.save(baos);
|
||||||
|
document.close();
|
||||||
|
|
||||||
|
byte[] pdfContent = baos.toByteArray();
|
||||||
|
return WebResponseUtils.bytesToWebResponse(pdfContent,
|
||||||
|
file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_redacted.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void redactFoundText(PDDocument document, List<PDFText> blocks, float customPadding, Color redactColor) throws IOException {
|
||||||
|
var allPages = document.getDocumentCatalog().getPages();
|
||||||
|
|
||||||
|
for (PDFText block : blocks) {
|
||||||
|
var page = allPages.get(block.getPageIndex());
|
||||||
|
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
|
contentStream.setNonStrokingColor(redactColor);
|
||||||
|
float padding = (block.getY2() - block.getY1()) * 0.3f + customPadding;
|
||||||
|
PDRectangle pageBox = page.getBBox();
|
||||||
|
contentStream.addRect(block.getX1(), pageBox.getHeight() - block.getY1() - padding, block.getX2() - block.getX1(), block.getY2() - block.getY1() + 2 * padding);
|
||||||
|
contentStream.fill();
|
||||||
|
contentStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,58 +1,51 @@
|
||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.cos.COSDictionary;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDResources;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageTree;
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDStream;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.action.*;
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDNonTerminalField;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDTerminalField;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
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 org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
import org.apache.pdfbox.cos.COSDictionary;
|
|
||||||
import org.apache.pdfbox.cos.COSName;
|
|
||||||
import org.apache.pdfbox.cos.COSString;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/security")
|
||||||
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class SanitizeController {
|
public class SanitizeController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
||||||
@Operation(summary = "Sanitize a PDF file",
|
@Operation(summary = "Sanitize a PDF file",
|
||||||
description = "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
description = "This endpoint processes a PDF file and removes specific elements based on the provided options. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> sanitizePDF(
|
public ResponseEntity<byte[]> sanitizePDF(@ModelAttribute SanitizePdfRequest request) throws IOException {
|
||||||
@RequestPart(required = true, value = "fileInput")
|
MultipartFile inputFile = request.getFileInput();
|
||||||
@Parameter(description = "The input PDF file to be sanitized")
|
boolean removeJavaScript = request.isRemoveJavaScript();
|
||||||
MultipartFile inputFile,
|
boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();
|
||||||
@RequestParam(name = "removeJavaScript", required = false, defaultValue = "true")
|
boolean removeMetadata = request.isRemoveMetadata();
|
||||||
@Parameter(description = "Remove JavaScript actions from the PDF if set to true")
|
boolean removeLinks = request.isRemoveLinks();
|
||||||
Boolean removeJavaScript,
|
boolean removeFonts = request.isRemoveFonts();
|
||||||
@RequestParam(name = "removeEmbeddedFiles", required = false, defaultValue = "true")
|
|
||||||
@Parameter(description = "Remove embedded files from the PDF if set to true")
|
|
||||||
Boolean removeEmbeddedFiles,
|
|
||||||
@RequestParam(name = "removeMetadata", required = false, defaultValue = "true")
|
|
||||||
@Parameter(description = "Remove metadata from the PDF if set to true")
|
|
||||||
Boolean removeMetadata,
|
|
||||||
@RequestParam(name = "removeLinks", required = false, defaultValue = "true")
|
|
||||||
@Parameter(description = "Remove links from the PDF if set to true")
|
|
||||||
Boolean removeLinks,
|
|
||||||
@RequestParam(name = "removeFonts", required = false, defaultValue = "true")
|
|
||||||
@Parameter(description = "Remove fonts from the PDF if set to true")
|
|
||||||
Boolean removeFonts) throws IOException {
|
|
||||||
|
|
||||||
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
try (PDDocument document = PDDocument.load(inputFile.getInputStream())) {
|
||||||
if (removeJavaScript) {
|
if (removeJavaScript) {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.RenderingHints;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -24,40 +22,35 @@ import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||||
import org.apache.pdfbox.util.Matrix;
|
import org.apache.pdfbox.util.Matrix;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import stirling.software.SPDF.model.api.security.AddWatermarkRequest;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/security")
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class WatermarkController {
|
public class WatermarkController {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
||||||
@Operation(summary = "Add watermark to a PDF file", description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
|
@Operation(summary = "Add watermark to a PDF file", description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO")
|
||||||
public ResponseEntity<byte[]> addWatermark(
|
public ResponseEntity<byte[]> addWatermark(@ModelAttribute AddWatermarkRequest request) throws IOException, Exception {
|
||||||
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to add a watermark") MultipartFile pdfFile,
|
MultipartFile pdfFile = request.getFileInput();
|
||||||
@RequestParam(required = true) @Parameter(description = "The watermark type (text or image)") String watermarkType,
|
String watermarkType = request.getWatermarkType();
|
||||||
@RequestParam(required = false) @Parameter(description = "The watermark text") String watermarkText,
|
String watermarkText = request.getWatermarkText();
|
||||||
@RequestPart(required = false) @Parameter(description = "The watermark image") MultipartFile watermarkImage,
|
MultipartFile watermarkImage = request.getWatermarkImage();
|
||||||
|
String alphabet = request.getAlphabet();
|
||||||
@RequestParam(defaultValue = "roman", name = "alphabet") @Parameter(description = "The selected alphabet",
|
float fontSize = request.getFontSize();
|
||||||
schema = @Schema(type = "string",
|
float rotation = request.getRotation();
|
||||||
allowableValues = {"roman","arabic","japanese","korean","chinese"},
|
float opacity = request.getOpacity();
|
||||||
defaultValue = "roman")) String alphabet,
|
int widthSpacer = request.getWidthSpacer();
|
||||||
@RequestParam(defaultValue = "30", name = "fontSize") @Parameter(description = "The font size of the watermark text", example = "30") float fontSize,
|
int heightSpacer = request.getHeightSpacer();
|
||||||
@RequestParam(defaultValue = "0", name = "rotation") @Parameter(description = "The rotation of the watermark in degrees", example = "0") float rotation,
|
|
||||||
@RequestParam(defaultValue = "0.5", name = "opacity") @Parameter(description = "The opacity of the watermark (0.0 - 1.0)", example = "0.5") float opacity,
|
|
||||||
@RequestParam(defaultValue = "50", name = "widthSpacer") @Parameter(description = "The width spacer between watermark elements", example = "50") int widthSpacer,
|
|
||||||
@RequestParam(defaultValue = "50", name = "heightSpacer") @Parameter(description = "The height spacer between watermark elements", example = "50") int heightSpacer)
|
|
||||||
throws IOException, Exception {
|
|
||||||
|
|
||||||
// Load the input PDF
|
// Load the input PDF
|
||||||
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
PDDocument document = PDDocument.load(pdfFile.getInputStream());
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
package stirling.software.SPDF.controller.web;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import stirling.software.SPDF.model.User;
|
||||||
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
@Controller
|
||||||
|
@Tag(name = "Account Security", description = "Account Security APIs")
|
||||||
|
public class AccountWebController {
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/login")
|
||||||
|
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
||||||
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getParameter("error") != null) {
|
||||||
|
|
||||||
|
model.addAttribute("error", request.getParameter("error"));
|
||||||
|
}
|
||||||
|
if (request.getParameter("logout") != null) {
|
||||||
|
|
||||||
|
model.addAttribute("logoutMessage", "You have been logged out.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "login";
|
||||||
|
}
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository; // Assuming you have a repository for user operations
|
||||||
|
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@GetMapping("/addUsers")
|
||||||
|
public String showAddUserForm(Model model, Authentication authentication) {
|
||||||
|
List<User> allUsers = userRepository.findAll();
|
||||||
|
model.addAttribute("users", allUsers);
|
||||||
|
model.addAttribute("currentUsername", authentication.getName());
|
||||||
|
return "addUsers";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/account")
|
||||||
|
public String account(HttpServletRequest request, Model model, Authentication authentication) {
|
||||||
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
|
||||||
|
if (principal instanceof UserDetails) {
|
||||||
|
// Cast the principal object to UserDetails
|
||||||
|
UserDetails userDetails = (UserDetails) principal;
|
||||||
|
|
||||||
|
// Retrieve username and other attributes
|
||||||
|
String username = userDetails.getUsername();
|
||||||
|
|
||||||
|
// Fetch user details from the database
|
||||||
|
Optional<User> user = userRepository.findByUsername(username); // Assuming findByUsername method exists
|
||||||
|
if (!user.isPresent()) {
|
||||||
|
// Handle error appropriately
|
||||||
|
return "redirect:/error"; // Example redirection in case of error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert settings map to JSON string
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
String settingsJson;
|
||||||
|
try {
|
||||||
|
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
// Handle JSON conversion error
|
||||||
|
e.printStackTrace();
|
||||||
|
return "redirect:/error"; // Example redirection in case of error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add attributes to the model
|
||||||
|
model.addAttribute("username", username);
|
||||||
|
model.addAttribute("role", user.get().getRolesAsString());
|
||||||
|
model.addAttribute("settings", settingsJson);
|
||||||
|
model.addAttribute("changeCredsFlag", user.get().isFirstLogin());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
return "account";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/change-creds")
|
||||||
|
public String changeCreds(HttpServletRequest request, Model model, Authentication authentication) {
|
||||||
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
|
||||||
|
if (principal instanceof UserDetails) {
|
||||||
|
// Cast the principal object to UserDetails
|
||||||
|
UserDetails userDetails = (UserDetails) principal;
|
||||||
|
|
||||||
|
// Retrieve username and other attributes
|
||||||
|
String username = userDetails.getUsername();
|
||||||
|
|
||||||
|
// Fetch user details from the database
|
||||||
|
Optional<User> user = userRepository.findByUsername(username); // Assuming findByUsername method exists
|
||||||
|
if (!user.isPresent()) {
|
||||||
|
// Handle error appropriately
|
||||||
|
return "redirect:/error"; // Example redirection in case of error
|
||||||
|
}
|
||||||
|
// Add attributes to the model
|
||||||
|
model.addAttribute("username", username);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
|
return "change-creds";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@ -20,14 +21,19 @@ import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@Tag(name = "General", description = "General APIs")
|
@Tag(name = "General", description = "General APIs")
|
||||||
public class GeneralWebController {
|
public class GeneralWebController {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/pipeline")
|
@GetMapping("/pipeline")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String pipelineForm(Model model) {
|
public String pipelineForm(Model model) {
|
||||||
|
@ -46,7 +52,8 @@ public class GeneralWebController {
|
||||||
}
|
}
|
||||||
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
||||||
for (String config : pipelineConfigs) {
|
for (String config : pipelineConfigs) {
|
||||||
Map<String, Object> jsonContent = new ObjectMapper().readValue(config, Map.class);
|
Map<String, Object> jsonContent = new ObjectMapper().readValue(config, new TypeReference<Map<String, Object>>(){});
|
||||||
|
|
||||||
String name = (String) jsonContent.get("name");
|
String name = (String) jsonContent.get("name");
|
||||||
Map<String, String> configWithName = new HashMap<>();
|
Map<String, String> configWithName = new HashMap<>();
|
||||||
configWithName.put("json", config);
|
configWithName.put("json", config);
|
||||||
|
@ -133,30 +140,116 @@ public class GeneralWebController {
|
||||||
return "sign";
|
return "sign";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/multi-page-layout")
|
||||||
|
@Hidden
|
||||||
|
public String multiPageLayoutForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "multi-page-layout");
|
||||||
|
return "multi-page-layout";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/scale-pages")
|
||||||
|
@Hidden
|
||||||
|
public String scalePagesFrom(Model model) {
|
||||||
|
model.addAttribute("currentPage", "scale-pages");
|
||||||
|
return "scale-pages";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ResourceLoader resourceLoader;
|
private ResourceLoader resourceLoader;
|
||||||
|
|
||||||
private List<String> getFontNames() {
|
private List<FontResource> getFontNames() {
|
||||||
|
List<FontResource> fontNames = new ArrayList<>();
|
||||||
|
|
||||||
|
// Extract font names from classpath
|
||||||
|
fontNames.addAll(getFontNamesFromLocation("classpath:static/fonts/*.woff2"));
|
||||||
|
|
||||||
|
// Extract font names from external directory
|
||||||
|
fontNames.addAll(getFontNamesFromLocation("file:customFiles/static/fonts/*"));
|
||||||
|
|
||||||
|
return fontNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FontResource> getFontNamesFromLocation(String locationPattern) {
|
||||||
try {
|
try {
|
||||||
Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader)
|
Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader)
|
||||||
.getResources("classpath:static/fonts/*.woff2");
|
.getResources(locationPattern);
|
||||||
|
|
||||||
return Arrays.stream(resources)
|
return Arrays.stream(resources)
|
||||||
.map(resource -> {
|
.map(resource -> {
|
||||||
try {
|
try {
|
||||||
String filename = resource.getFilename();
|
String filename = resource.getFilename();
|
||||||
return filename.substring(0, filename.length() - 6); // Remove .woff2 extension
|
if (filename != null) {
|
||||||
|
int lastDotIndex = filename.lastIndexOf('.');
|
||||||
|
if (lastDotIndex != -1) {
|
||||||
|
String name = filename.substring(0, lastDotIndex);
|
||||||
|
String extension = filename.substring(lastDotIndex + 1);
|
||||||
|
return new FontResource(name, extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Error processing filename", e);
|
throw new RuntimeException("Error processing filename", e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to read font directory", e);
|
throw new RuntimeException("Failed to read font directory from " + locationPattern, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getFormatFromExtension(String extension) {
|
||||||
|
switch (extension) {
|
||||||
|
case "ttf": return "truetype";
|
||||||
|
case "woff": return "woff";
|
||||||
|
case "woff2": return "woff2";
|
||||||
|
case "eot": return "embedded-opentype";
|
||||||
|
case "svg": return "svg";
|
||||||
|
default: return ""; // or throw an exception if an unexpected extension is encountered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class FontResource {
|
||||||
|
private String name;
|
||||||
|
private String extension;
|
||||||
|
private String type;
|
||||||
|
public FontResource(String name, String extension) {
|
||||||
|
this.name = name;
|
||||||
|
this.extension = extension;
|
||||||
|
this.type = getFormatFromExtension(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExtension() {
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtension(String extension) {
|
||||||
|
this.extension = extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/crop")
|
@GetMapping("/crop")
|
||||||
@Hidden
|
@Hidden
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
@ -7,6 +8,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class HomeWebController {
|
public class HomeWebController {
|
||||||
|
@ -31,18 +33,16 @@ public class HomeWebController {
|
||||||
return "redirect:/";
|
return "redirect:/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
|
||||||
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
|
@GetMapping(value = "/robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@Hidden
|
@Hidden
|
||||||
public String getRobotsTxt() {
|
public String getRobotsTxt() {
|
||||||
String allowGoogleVisibility = System.getProperty("ALLOW_GOOGLE_VISIBILITY");
|
Boolean allowGoogle = applicationProperties.getSystem().getGooglevisibility();
|
||||||
if (allowGoogleVisibility == null)
|
if(Boolean.TRUE.equals(allowGoogle)) {
|
||||||
allowGoogleVisibility = System.getenv("ALLOW_GOOGLE_VISIBILITY");
|
|
||||||
if (allowGoogleVisibility == null)
|
|
||||||
allowGoogleVisibility = "false";
|
|
||||||
if (Boolean.parseBoolean(allowGoogleVisibility)) {
|
|
||||||
return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /";
|
return "User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /";
|
||||||
} else {
|
} else {
|
||||||
return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /";
|
return "User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /";
|
||||||
|
|
|
@ -8,7 +8,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
@ -24,26 +24,28 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import stirling.software.SPDF.config.StartupApplicationListener;
|
import stirling.software.SPDF.config.StartupApplicationListener;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1")
|
@RequestMapping("/api/v1")
|
||||||
@Tag(name = "API", description = "Info APIs")
|
@Tag(name = "API", description = "Info APIs")
|
||||||
public class MetricsController {
|
public class MetricsController {
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
|
||||||
private final MeterRegistry meterRegistry;
|
private final MeterRegistry meterRegistry;
|
||||||
|
|
||||||
private boolean isEndpointEnabled;
|
private boolean metricsEnabled;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
String isEndpointEnabled = System.getProperty("ENABLE_API_METRICS");
|
Boolean metricsEnabled = applicationProperties.getMetrics().getEnabled();
|
||||||
if (isEndpointEnabled == null) {
|
if(metricsEnabled == null)
|
||||||
isEndpointEnabled = System.getenv("ENABLE_API_METRICS");
|
metricsEnabled = true;
|
||||||
if (isEndpointEnabled == null) {
|
this.metricsEnabled = metricsEnabled;
|
||||||
isEndpointEnabled = "true";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.isEndpointEnabled = "true".equalsIgnoreCase(isEndpointEnabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MetricsController(MeterRegistry meterRegistry) {
|
public MetricsController(MeterRegistry meterRegistry) {
|
||||||
|
@ -54,7 +56,7 @@ public class MetricsController {
|
||||||
@Operation(summary = "Application status and version",
|
@Operation(summary = "Application status and version",
|
||||||
description = "This endpoint returns the status of the application and its version number.")
|
description = "This endpoint returns the status of the application and its version number.")
|
||||||
public ResponseEntity<?> getStatus() {
|
public ResponseEntity<?> getStatus() {
|
||||||
if (!isEndpointEnabled) {
|
if (!metricsEnabled) {
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +70,7 @@ public class MetricsController {
|
||||||
@Operation(summary = "GET request count",
|
@Operation(summary = "GET request count",
|
||||||
description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.")
|
description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.")
|
||||||
public ResponseEntity<?> getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
|
public ResponseEntity<?> getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
|
||||||
if (!isEndpointEnabled) {
|
if (!metricsEnabled) {
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -109,7 +111,7 @@ public class MetricsController {
|
||||||
@Operation(summary = "GET requests count for all endpoints",
|
@Operation(summary = "GET requests count for all endpoints",
|
||||||
description = "This endpoint returns the count of GET requests for each endpoint.")
|
description = "This endpoint returns the count of GET requests for each endpoint.")
|
||||||
public ResponseEntity<?> getAllEndpointLoads() {
|
public ResponseEntity<?> getAllEndpointLoads() {
|
||||||
if (!isEndpointEnabled) {
|
if (!metricsEnabled) {
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -170,7 +172,7 @@ public class MetricsController {
|
||||||
@Operation(summary = "POST request count",
|
@Operation(summary = "POST request count",
|
||||||
description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.")
|
description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.")
|
||||||
public ResponseEntity<?> getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
|
public ResponseEntity<?> getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
|
||||||
if (!isEndpointEnabled) {
|
if (!metricsEnabled) {
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -208,7 +210,7 @@ public class MetricsController {
|
||||||
@Operation(summary = "POST requests count for all endpoints",
|
@Operation(summary = "POST requests count for all endpoints",
|
||||||
description = "This endpoint returns the count of POST requests for each endpoint.")
|
description = "This endpoint returns the count of POST requests for each endpoint.")
|
||||||
public ResponseEntity<?> getAllPostRequests() {
|
public ResponseEntity<?> getAllPostRequests() {
|
||||||
if (!isEndpointEnabled) {
|
if (!metricsEnabled) {
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -244,7 +246,7 @@ public class MetricsController {
|
||||||
|
|
||||||
@GetMapping("/uptime")
|
@GetMapping("/uptime")
|
||||||
public ResponseEntity<?> getUptime() {
|
public ResponseEntity<?> getUptime() {
|
||||||
if (!isEndpointEnabled) {
|
if (!metricsEnabled) {
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,19 +15,19 @@ import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@Tag(name = "Other", description = "Other APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
public class OtherWebController {
|
public class OtherWebController {
|
||||||
@GetMapping("/compress-pdf")
|
@GetMapping("/compress-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String compressPdfForm(Model model) {
|
public String compressPdfForm(Model model) {
|
||||||
model.addAttribute("currentPage", "compress-pdf");
|
model.addAttribute("currentPage", "compress-pdf");
|
||||||
return "other/compress-pdf";
|
return "misc/compress-pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/extract-image-scans")
|
@GetMapping("/extract-image-scans")
|
||||||
@Hidden
|
@Hidden
|
||||||
public ModelAndView extractImageScansForm() {
|
public ModelAndView extractImageScansForm() {
|
||||||
ModelAndView modelAndView = new ModelAndView("other/extract-image-scans");
|
ModelAndView modelAndView = new ModelAndView("misc/extract-image-scans");
|
||||||
modelAndView.addObject("currentPage", "extract-image-scans");
|
modelAndView.addObject("currentPage", "extract-image-scans");
|
||||||
return modelAndView;
|
return modelAndView;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ public class OtherWebController {
|
||||||
@Hidden
|
@Hidden
|
||||||
public String extractJavascriptForm(Model model) {
|
public String extractJavascriptForm(Model model) {
|
||||||
model.addAttribute("currentPage", "show-javascript");
|
model.addAttribute("currentPage", "show-javascript");
|
||||||
return "other/show-javascript";
|
return "misc/show-javascript";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,21 +44,21 @@ public class OtherWebController {
|
||||||
@Hidden
|
@Hidden
|
||||||
public String addPageNumbersForm(Model model) {
|
public String addPageNumbersForm(Model model) {
|
||||||
model.addAttribute("currentPage", "add-page-numbers");
|
model.addAttribute("currentPage", "add-page-numbers");
|
||||||
return "other/add-page-numbers";
|
return "misc/add-page-numbers";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/extract-images")
|
@GetMapping("/extract-images")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String extractImagesForm(Model model) {
|
public String extractImagesForm(Model model) {
|
||||||
model.addAttribute("currentPage", "extract-images");
|
model.addAttribute("currentPage", "extract-images");
|
||||||
return "other/extract-images";
|
return "misc/extract-images";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/flatten")
|
@GetMapping("/flatten")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String flattenForm(Model model) {
|
public String flattenForm(Model model) {
|
||||||
model.addAttribute("currentPage", "flatten");
|
model.addAttribute("currentPage", "flatten");
|
||||||
return "other/flatten";
|
return "misc/flatten";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,14 +67,14 @@ public class OtherWebController {
|
||||||
@Hidden
|
@Hidden
|
||||||
public String addWatermarkForm(Model model) {
|
public String addWatermarkForm(Model model) {
|
||||||
model.addAttribute("currentPage", "change-metadata");
|
model.addAttribute("currentPage", "change-metadata");
|
||||||
return "other/change-metadata";
|
return "misc/change-metadata";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/compare")
|
@GetMapping("/compare")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String compareForm(Model model) {
|
public String compareForm(Model model) {
|
||||||
model.addAttribute("currentPage", "compare");
|
model.addAttribute("currentPage", "compare");
|
||||||
return "other/compare";
|
return "misc/compare";
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getAvailableTesseractLanguages() {
|
public List<String> getAvailableTesseractLanguages() {
|
||||||
|
@ -90,7 +90,7 @@ public class OtherWebController {
|
||||||
@GetMapping("/ocr-pdf")
|
@GetMapping("/ocr-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public ModelAndView ocrPdfPage() {
|
public ModelAndView ocrPdfPage() {
|
||||||
ModelAndView modelAndView = new ModelAndView("other/ocr-pdf");
|
ModelAndView modelAndView = new ModelAndView("misc/ocr-pdf");
|
||||||
List<String> languages = getAvailableTesseractLanguages();
|
List<String> languages = getAvailableTesseractLanguages();
|
||||||
Collections.sort(languages);
|
Collections.sort(languages);
|
||||||
modelAndView.addObject("languages", languages);
|
modelAndView.addObject("languages", languages);
|
||||||
|
@ -103,56 +103,43 @@ public class OtherWebController {
|
||||||
@Hidden
|
@Hidden
|
||||||
public String overlayImage(Model model) {
|
public String overlayImage(Model model) {
|
||||||
model.addAttribute("currentPage", "add-image");
|
model.addAttribute("currentPage", "add-image");
|
||||||
return "other/add-image";
|
return "misc/add-image";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/adjust-contrast")
|
@GetMapping("/adjust-contrast")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String contrast(Model model) {
|
public String contrast(Model model) {
|
||||||
model.addAttribute("currentPage", "adjust-contrast");
|
model.addAttribute("currentPage", "adjust-contrast");
|
||||||
return "other/adjust-contrast";
|
return "misc/adjust-contrast";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/repair")
|
@GetMapping("/repair")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String repairForm(Model model) {
|
public String repairForm(Model model) {
|
||||||
model.addAttribute("currentPage", "repair");
|
model.addAttribute("currentPage", "repair");
|
||||||
return "other/repair";
|
return "misc/repair";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/remove-blanks")
|
@GetMapping("/remove-blanks")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String removeBlanksForm(Model model) {
|
public String removeBlanksForm(Model model) {
|
||||||
model.addAttribute("currentPage", "remove-blanks");
|
model.addAttribute("currentPage", "remove-blanks");
|
||||||
return "other/remove-blanks";
|
return "misc/remove-blanks";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/multi-page-layout")
|
|
||||||
@Hidden
|
|
||||||
public String multiPageLayoutForm(Model model) {
|
|
||||||
model.addAttribute("currentPage", "multi-page-layout");
|
|
||||||
return "other/multi-page-layout";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/scale-pages")
|
|
||||||
@Hidden
|
|
||||||
public String scalePagesFrom(Model model) {
|
|
||||||
model.addAttribute("currentPage", "scale-pages");
|
|
||||||
return "other/scale-pages";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/auto-crop")
|
@GetMapping("/auto-crop")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String autoCropForm(Model model) {
|
public String autoCropForm(Model model) {
|
||||||
model.addAttribute("currentPage", "auto-crop");
|
model.addAttribute("currentPage", "auto-crop");
|
||||||
return "other/auto-crop";
|
return "misc/auto-crop";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/auto-rename")
|
@GetMapping("/auto-rename")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String autoRenameForm(Model model) {
|
public String autoRenameForm(Model model) {
|
||||||
model.addAttribute("currentPage", "auto-rename");
|
model.addAttribute("currentPage", "auto-rename");
|
||||||
return "other/auto-rename";
|
return "misc/auto-rename";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,12 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@Tag(name = "Security", description = "Security APIs")
|
@Tag(name = "Security", description = "Security APIs")
|
||||||
public class SecurityWebController {
|
public class SecurityWebController {
|
||||||
|
|
||||||
|
@GetMapping("/auto-redact")
|
||||||
|
@Hidden
|
||||||
|
public String autoRedactForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "auto-redact");
|
||||||
|
return "security/auto-redact";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/add-password")
|
@GetMapping("/add-password")
|
||||||
@Hidden
|
@Hidden
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken {
|
||||||
|
|
||||||
|
private final Object principal;
|
||||||
|
private Object credentials;
|
||||||
|
|
||||||
|
public ApiKeyAuthenticationToken(String apiKey) {
|
||||||
|
super(null);
|
||||||
|
this.principal = null;
|
||||||
|
this.credentials = apiKey;
|
||||||
|
setAuthenticated(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiKeyAuthenticationToken(Object principal, String apiKey, Collection<? extends GrantedAuthority> authorities) {
|
||||||
|
super(authorities);
|
||||||
|
this.principal = principal; // principal can be a UserDetails object
|
||||||
|
this.credentials = apiKey;
|
||||||
|
super.setAuthenticated(true); // this authentication is trusted
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
|
||||||
|
if (isAuthenticated) {
|
||||||
|
throw new IllegalArgumentException("Cannot set this token to trusted. Use constructor which takes a GrantedAuthority list instead.");
|
||||||
|
}
|
||||||
|
super.setAuthenticated(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eraseCredentials() {
|
||||||
|
super.eraseCredentials();
|
||||||
|
credentials = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,335 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "")
|
||||||
|
@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class)
|
||||||
|
public class ApplicationProperties {
|
||||||
|
private Security security;
|
||||||
|
private System system;
|
||||||
|
private Ui ui;
|
||||||
|
private Endpoints endpoints;
|
||||||
|
private Metrics metrics;
|
||||||
|
private AutomaticallyGenerated automaticallyGenerated;
|
||||||
|
private AutoPipeline autoPipeline;
|
||||||
|
|
||||||
|
public AutoPipeline getAutoPipeline() {
|
||||||
|
return autoPipeline != null ? autoPipeline : new AutoPipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoPipeline(AutoPipeline autoPipeline) {
|
||||||
|
this.autoPipeline = autoPipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Security getSecurity() {
|
||||||
|
return security != null ? security : new Security();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecurity(Security security) {
|
||||||
|
this.security = security;
|
||||||
|
}
|
||||||
|
|
||||||
|
public System getSystem() {
|
||||||
|
return system != null ? system : new System();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSystem(System system) {
|
||||||
|
this.system = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Ui getUi() {
|
||||||
|
return ui != null ? ui : new Ui();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUi(Ui ui) {
|
||||||
|
this.ui = ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Endpoints getEndpoints() {
|
||||||
|
return endpoints != null ? endpoints : new Endpoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndpoints(Endpoints endpoints) {
|
||||||
|
this.endpoints = endpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Metrics getMetrics() {
|
||||||
|
return metrics != null ? metrics : new Metrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetrics(Metrics metrics) {
|
||||||
|
this.metrics = metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutomaticallyGenerated getAutomaticallyGenerated() {
|
||||||
|
return automaticallyGenerated != null ? automaticallyGenerated : new AutomaticallyGenerated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutomaticallyGenerated(AutomaticallyGenerated automaticallyGenerated) {
|
||||||
|
this.automaticallyGenerated = automaticallyGenerated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ApplicationProperties [security=" + security + ", system=" + system + ", ui=" + ui + ", endpoints="
|
||||||
|
+ endpoints + ", metrics=" + metrics + ", automaticallyGenerated=" + automaticallyGenerated
|
||||||
|
+ ", autoPipeline=" + autoPipeline + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AutoPipeline {
|
||||||
|
private String outputFolder;
|
||||||
|
|
||||||
|
public String getOutputFolder() {
|
||||||
|
return outputFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputFolder(String outputFolder) {
|
||||||
|
this.outputFolder = outputFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AutoPipeline [outputFolder=" + outputFolder + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
public static class Security {
|
||||||
|
private Boolean enableLogin;
|
||||||
|
private Boolean csrfDisabled;
|
||||||
|
private InitialLogin initialLogin;
|
||||||
|
|
||||||
|
public InitialLogin getInitialLogin() {
|
||||||
|
return initialLogin != null ? initialLogin : new InitialLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitialLogin(InitialLogin initialLogin) {
|
||||||
|
this.initialLogin = initialLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getEnableLogin() {
|
||||||
|
return enableLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableLogin(Boolean enableLogin) {
|
||||||
|
this.enableLogin = enableLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getCsrfDisabled() {
|
||||||
|
return csrfDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCsrfDisabled(Boolean csrfDisabled) {
|
||||||
|
this.csrfDisabled = csrfDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Security [enableLogin=" + enableLogin + ", initialLogin=" + initialLogin + ", csrfDisabled="
|
||||||
|
+ csrfDisabled + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InitialLogin {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "InitialLogin [username=" + username + ", password=" + (password != null && !password.isEmpty() ? "MASKED" : "NULL") + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class System {
|
||||||
|
private String defaultLocale;
|
||||||
|
private Boolean googlevisibility;
|
||||||
|
private String rootURIPath;
|
||||||
|
private String customStaticFilePath;
|
||||||
|
private Integer maxFileSize;
|
||||||
|
|
||||||
|
public String getDefaultLocale() {
|
||||||
|
return defaultLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultLocale(String defaultLocale) {
|
||||||
|
this.defaultLocale = defaultLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getGooglevisibility() {
|
||||||
|
return googlevisibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGooglevisibility(Boolean googlevisibility) {
|
||||||
|
this.googlevisibility = googlevisibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRootURIPath() {
|
||||||
|
return rootURIPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRootURIPath(String rootURIPath) {
|
||||||
|
this.rootURIPath = rootURIPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomStaticFilePath() {
|
||||||
|
return customStaticFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomStaticFilePath(String customStaticFilePath) {
|
||||||
|
this.customStaticFilePath = customStaticFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMaxFileSize() {
|
||||||
|
return maxFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxFileSize(Integer maxFileSize) {
|
||||||
|
this.maxFileSize = maxFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "System [defaultLocale=" + defaultLocale + ", googlevisibility=" + googlevisibility + ", rootURIPath="
|
||||||
|
+ rootURIPath + ", customStaticFilePath=" + customStaticFilePath + ", maxFileSize=" + maxFileSize
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Ui {
|
||||||
|
private String appName;
|
||||||
|
private String homeDescription;
|
||||||
|
private String appNameNavbar;
|
||||||
|
|
||||||
|
public String getAppName() {
|
||||||
|
if(appName != null && appName.trim().length() == 0)
|
||||||
|
return null;
|
||||||
|
return appName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppName(String appName) {
|
||||||
|
this.appName = appName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHomeDescription() {
|
||||||
|
if(homeDescription != null && homeDescription.trim().length() == 0)
|
||||||
|
return null;
|
||||||
|
return homeDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHomeDescription(String homeDescription) {
|
||||||
|
this.homeDescription = homeDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAppNameNavbar() {
|
||||||
|
if(appNameNavbar != null && appNameNavbar.trim().length() == 0)
|
||||||
|
return null;
|
||||||
|
return appNameNavbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppNameNavbar(String appNameNavbar) {
|
||||||
|
this.appNameNavbar = appNameNavbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "UserInterface [appName=" + appName + ", homeDescription=" + homeDescription + ", appNameNavbar=" + appNameNavbar + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class Endpoints {
|
||||||
|
private List<String> toRemove;
|
||||||
|
private List<String> groupsToRemove;
|
||||||
|
|
||||||
|
public List<String> getToRemove() {
|
||||||
|
return toRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToRemove(List<String> toRemove) {
|
||||||
|
this.toRemove = toRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getGroupsToRemove() {
|
||||||
|
return groupsToRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupsToRemove(List<String> groupsToRemove) {
|
||||||
|
this.groupsToRemove = groupsToRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Endpoints [toRemove=" + toRemove + ", groupsToRemove=" + groupsToRemove + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Metrics {
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
public Boolean getEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(Boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Metrics [enabled=" + enabled + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AutomaticallyGenerated {
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AutomaticallyGenerated [key=" + (key != null && !key.isEmpty() ? "MASKED" : "NULL") + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
64
src/main/java/stirling/software/SPDF/model/Authority.java
Normal file
64
src/main/java/stirling/software/SPDF/model/Authority.java
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "authorities")
|
||||||
|
public class Authority {
|
||||||
|
|
||||||
|
public Authority() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Authority(String authority, User user) {
|
||||||
|
this.authority = authority;
|
||||||
|
this.user = user;
|
||||||
|
user.getAuthorities().add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "authority")
|
||||||
|
private String authority;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "user_id")
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthority() {
|
||||||
|
return authority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthority(String authority) {
|
||||||
|
this.authority = authority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(User user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
42
src/main/java/stirling/software/SPDF/model/PDFText.java
Normal file
42
src/main/java/stirling/software/SPDF/model/PDFText.java
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
public class PDFText {
|
||||||
|
private final int pageIndex;
|
||||||
|
private final float x1;
|
||||||
|
private final float y1;
|
||||||
|
private final float x2;
|
||||||
|
private final float y2;
|
||||||
|
private final String text;
|
||||||
|
|
||||||
|
public PDFText(int pageIndex, float x1, float y1, float x2, float y2, String text) {
|
||||||
|
this.pageIndex = pageIndex;
|
||||||
|
this.x1 = x1;
|
||||||
|
this.y1 = y1;
|
||||||
|
this.x2 = x2;
|
||||||
|
this.y2 = y2;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageIndex() {
|
||||||
|
return pageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getX1() {
|
||||||
|
return x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getY1() {
|
||||||
|
return y1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getX2() {
|
||||||
|
return x2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getY2() {
|
||||||
|
return y2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "persistent_logins")
|
||||||
|
public class PersistentLogin {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "series")
|
||||||
|
private String series;
|
||||||
|
|
||||||
|
@Column(name = "username", length = 64, nullable = false)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Column(name = "token", length = 64, nullable = false)
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Column(name = "last_used", nullable = false)
|
||||||
|
private Date lastUsed;
|
||||||
|
|
||||||
|
public String getSeries() {
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeries(String series) {
|
||||||
|
this.series = series;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getLastUsed() {
|
||||||
|
return lastUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastUsed(Date lastUsed) {
|
||||||
|
this.lastUsed = lastUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Getters, setters, etc.
|
||||||
|
}
|
50
src/main/java/stirling/software/SPDF/model/Role.java
Normal file
50
src/main/java/stirling/software/SPDF/model/Role.java
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
public enum Role {
|
||||||
|
|
||||||
|
// Unlimited access
|
||||||
|
ADMIN("ROLE_ADMIN", Integer.MAX_VALUE, Integer.MAX_VALUE),
|
||||||
|
|
||||||
|
// Unlimited access
|
||||||
|
USER("ROLE_USER", Integer.MAX_VALUE, Integer.MAX_VALUE),
|
||||||
|
|
||||||
|
// 40 API calls Per Day, 40 web calls
|
||||||
|
LIMITED_API_USER("ROLE_LIMITED_API_USER", 40, 40),
|
||||||
|
|
||||||
|
// 20 API calls Per Day, 20 web calls
|
||||||
|
EXTRA_LIMITED_API_USER("ROLE_EXTRA_LIMITED_API_USER", 20, 20),
|
||||||
|
|
||||||
|
// 0 API calls per day and 20 web calls
|
||||||
|
WEB_ONLY_USER("ROLE_WEB_ONLY_USER", 0, 20);
|
||||||
|
|
||||||
|
private final String roleId;
|
||||||
|
private final int apiCallsPerDay;
|
||||||
|
private final int webCallsPerDay;
|
||||||
|
|
||||||
|
Role(String roleId, int apiCallsPerDay, int webCallsPerDay) {
|
||||||
|
this.roleId = roleId;
|
||||||
|
this.apiCallsPerDay = apiCallsPerDay;
|
||||||
|
this.webCallsPerDay = webCallsPerDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoleId() {
|
||||||
|
return roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getApiCallsPerDay() {
|
||||||
|
return apiCallsPerDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWebCallsPerDay() {
|
||||||
|
return webCallsPerDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Role fromString(String roleId) {
|
||||||
|
for (Role role : Role.values()) {
|
||||||
|
if (role.getRoleId().equalsIgnoreCase(roleId)) {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("No Role defined for id: " + roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
public enum SortTypes {
|
||||||
|
REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, SIDE_STITCH_BOOKLET_SORT, ODD_EVEN_SPLIT, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST,
|
||||||
|
}
|
134
src/main/java/stirling/software/SPDF/model/User.java
Normal file
134
src/main/java/stirling/software/SPDF/model/User.java
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.CollectionTable;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.ElementCollection;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.MapKeyColumn;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
@Entity
|
||||||
|
@Table(name = "users")
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "user_id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "username", unique = true)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Column(name = "password")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Column(name = "apiKey")
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
@Column(name = "enabled")
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Column(name = "isFirstLogin")
|
||||||
|
private Boolean isFirstLogin = false;
|
||||||
|
|
||||||
|
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
|
||||||
|
private Set<Authority> authorities = new HashSet<>();
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@MapKeyColumn(name = "setting_key")
|
||||||
|
@Column(name = "setting_value")
|
||||||
|
@CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id"))
|
||||||
|
private Map<String, String> settings = new HashMap<>(); // Key-value pairs of settings.
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isFirstLogin() {
|
||||||
|
return isFirstLogin != null && isFirstLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstLogin(boolean isFirstLogin) {
|
||||||
|
this.isFirstLogin = isFirstLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiKey() {
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApiKey(String apiKey) {
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getSettings() {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSettings(Map<String, String> settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Authority> getAuthorities() {
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorities(Set<Authority> authorities) {
|
||||||
|
this.authorities = authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAuthorities(Set<Authority> authorities) {
|
||||||
|
this.authorities.addAll(authorities);
|
||||||
|
}
|
||||||
|
public void addAuthority(Authority authorities) {
|
||||||
|
this.authorities.add(authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRolesAsString() {
|
||||||
|
return this.authorities.stream()
|
||||||
|
.map(Authority::getAuthority)
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package stirling.software.SPDF.model.api;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class GeneralFile {
|
||||||
|
|
||||||
|
@Schema(description = "The input file")
|
||||||
|
private MultipartFile fileInput;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package stirling.software.SPDF.model.api;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode
|
||||||
|
public class HandleDataRequest {
|
||||||
|
|
||||||
|
@Schema(description = "The input files")
|
||||||
|
private MultipartFile[] fileInputs;
|
||||||
|
|
||||||
|
@Schema(description = "JSON String")
|
||||||
|
private String jsonString;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package stirling.software.SPDF.model.api;
|
||||||
|
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode
|
||||||
|
public class ImageFile {
|
||||||
|
@Schema(description = "The input image file")
|
||||||
|
private MultipartFile fileInput;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue