mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-24 12:36:13 +00:00
Compare commits
26 Commits
4b6ac87419
...
cb7471024b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cb7471024b | ||
![]() |
74870615df | ||
![]() |
02d096d622 | ||
![]() |
0d7649bee8 | ||
![]() |
0776ecc96b | ||
![]() |
8113728d3d | ||
![]() |
528968bfe9 | ||
![]() |
61b85a9273 | ||
![]() |
c055f9456a | ||
![]() |
fe84b3ff15 | ||
![]() |
9a213c4bf6 | ||
![]() |
a4a57cef92 | ||
![]() |
6f6f4a14dc | ||
![]() |
c50aadeb35 | ||
![]() |
963b4ee69d | ||
![]() |
cd76f5e50a | ||
![]() |
763d50ba8d | ||
![]() |
4987932f60 | ||
![]() |
1036befaf1 | ||
![]() |
8a4acd4c98 | ||
![]() |
f93d8511e8 | ||
![]() |
54c7b0e689 | ||
![]() |
58ca41e5c5 | ||
![]() |
bf90f4b1da | ||
![]() |
7e276e8406 | ||
![]() |
18e2078b8b |
6
.github/config/.files.yaml
vendored
6
.github/config/.files.yaml
vendored
@ -30,3 +30,9 @@ project: &project
|
||||
- frontend/**
|
||||
- docker/**
|
||||
- testing/**
|
||||
|
||||
frontend: &frontend
|
||||
- frontend/**
|
||||
- .github/workflows/testdriver.yml
|
||||
- testing/**
|
||||
- docker/**
|
||||
|
@ -44,7 +44,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
@ -132,7 +132,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
@ -144,13 +144,13 @@ jobs:
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: refs/pull/${{ needs.check-comment.outputs.pr_number }}/merge
|
||||
token: ${{ steps.setup-bot.outputs.token }}
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
2
.github/workflows/PR-Demo-cleanup.yml
vendored
2
.github/workflows/PR-Demo-cleanup.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
|
2
.github/workflows/ai_pr_title_review.yml
vendored
2
.github/workflows/ai_pr_title_review.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
2
.github/workflows/auto-labelerV2.yml
vendored
2
.github/workflows/auto-labelerV2.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
|
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
||||
project: ${{ steps.changes.outputs.project }}
|
||||
openapi: ${{ steps.changes.outputs.openapi }}
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Check for file changes
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
@ -61,10 +61,10 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK ${{ matrix.jdk-version }}
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: ${{ matrix.jdk-version }}
|
||||
distribution: "temurin"
|
||||
@ -134,10 +134,10 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
@ -167,10 +167,10 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
@ -216,10 +216,10 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up Java 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
@ -265,10 +265,10 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
2
.github/workflows/check_properties.yml
vendored
2
.github/workflows/check_properties.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout main branch first
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
|
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@ -22,6 +22,6 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: "Dependency Review"
|
||||
uses: actions/dependency-review-action@bc41886e18ea39df68b1b1245f4184881938e050 # v4.7.2
|
||||
uses: actions/dependency-review-action@595b5aeba73380359d98a5e087f648dbb0edce1b # v4.7.3
|
||||
|
4
.github/workflows/licenses-update.yml
vendored
4
.github/workflows/licenses-update.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -48,7 +48,7 @@ jobs:
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
2
.github/workflows/manage-label.yml
vendored
2
.github/workflows/manage-label.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Run Labeler
|
||||
uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5.3.0
|
||||
|
12
.github/workflows/multiOSReleases.yml
vendored
12
.github/workflows/multiOSReleases.yml
vendored
@ -25,10 +25,10 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
@ -64,10 +64,10 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
@ -152,10 +152,10 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
4
.github/workflows/pre_commit.yml
vendored
4
.github/workflows/pre_commit.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -48,7 +48,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: "temurin"
|
||||
|
4
.github/workflows/push-docker.yml
vendored
4
.github/workflows/push-docker.yml
vendored
@ -34,10 +34,10 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
4
.github/workflows/releaseArtifacts.yml
vendored
4
.github/workflows/releaseArtifacts.yml
vendored
@ -27,10 +27,10 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
4
.github/workflows/scorecards.yml
vendored
4
.github/workflows/scorecards.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@ -74,6 +74,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5
|
||||
uses: github/codeql-action/upload-sarif@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d # v3.29.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
2
.github/workflows/sonarqube.yml
vendored
2
.github/workflows/sonarqube.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
4
.github/workflows/swagger.yml
vendored
4
.github/workflows/swagger.yml
vendored
@ -30,10 +30,10 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
2
.github/workflows/sync_files.yml
vendored
2
.github/workflows/sync_files.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
|
29
.github/workflows/testdriver.yml
vendored
29
.github/workflows/testdriver.yml
vendored
@ -29,10 +29,10 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@ -116,8 +116,25 @@ jobs:
|
||||
docker-compose up -d
|
||||
EOF
|
||||
|
||||
files-changed:
|
||||
if: always()
|
||||
name: detect what files changed
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
outputs:
|
||||
frontend: ${{ steps.changes.outputs.frontend }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Check for file changes
|
||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
id: changes
|
||||
with:
|
||||
filters: ".github/config/.files.yaml"
|
||||
|
||||
test:
|
||||
needs: deploy
|
||||
if: needs.files-changed.outputs.frontend == 'true'
|
||||
needs: [deploy, files-changed]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -126,18 +143,20 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Run TestDriver.ai
|
||||
uses: testdriverai/action@f0d0f45fdd684db628baa843fe9313f3ca3a8aa8 #1.1.3
|
||||
with:
|
||||
key: ${{secrets.TESTDRIVER_API_KEY}}
|
||||
prerun: |
|
||||
cd frontend
|
||||
npm install
|
||||
npm run build
|
||||
npm install dashcam-chrome --save
|
||||
@ -167,6 +186,7 @@ jobs:
|
||||
sudo chmod 600 ../private.key
|
||||
|
||||
- name: Cleanup deployment
|
||||
if: always()
|
||||
run: |
|
||||
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << EOF
|
||||
cd /stirling/test-${{ github.sha }}
|
||||
@ -174,3 +194,4 @@ jobs:
|
||||
cd /stirling
|
||||
rm -rf test-${{ github.sha }}
|
||||
EOF
|
||||
continue-on-error: true # Ensure cleanup runs even if previous steps fail
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -200,3 +200,6 @@ id_ed25519.pub
|
||||
|
||||
# node_modules
|
||||
node_modules/
|
||||
|
||||
# weasyPrint
|
||||
**/LOCAL_APPDATA_FONTCONFIG_CACHE/**
|
||||
|
@ -5,7 +5,7 @@
|
||||
The newly introduced feature enhances the application with robust database backup and import capabilities. This feature is designed to ensure data integrity and provide a straightforward way to manage database backups. Here's how it works:
|
||||
|
||||
1. Automatic Backup Creation
|
||||
- The system automatically creates a database backup every day at midnight. This ensures that there is always a recent backup available, minimizing the risk of data loss.
|
||||
- The system automatically creates a database backup on a configurable schedule (default: daily at midnight via `system.databaseBackup.cron`). This ensures that there is always a recent backup available, minimizing the risk of data loss.
|
||||
2. Manual Backup Export
|
||||
- Admin actions that modify the user database trigger a manual export of the database. This keeps the backup up-to-date with the latest changes and provides an extra layer of data security.
|
||||
3. Importing Database Backups
|
||||
|
14
README.md
14
README.md
@ -120,14 +120,14 @@ Stirling-PDF currently supports 40 languages!
|
||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
| Dutch (Nederlands) (nl_NL) |  |
|
||||
| English (English) (en_GB) |  |
|
||||
| English (US) (en_US) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| French (Français) (fr_FR) |  |
|
||||
| German (Deutsch) (de_DE) |  |
|
||||
| Greek (Ελληνικά) (el_GR) |  |
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
@ -135,12 +135,12 @@ Stirling-PDF currently supports 40 languages!
|
||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Persian (فارسی) (fa_IR) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
@ -152,9 +152,9 @@ Stirling-PDF currently supports 40 languages!
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Tibetan (བོད་ཡིག་) (bo_CN) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
| Malayalam (മലയാളം) (ml_IN) |  |
|
||||
|
||||
|
@ -39,7 +39,7 @@ dependencies {
|
||||
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
|
||||
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
|
||||
api 'org.snakeyaml:snakeyaml-engine:2.10'
|
||||
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.11"
|
||||
api 'jakarta.mail:jakarta.mail-api:2.1.3'
|
||||
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.12"
|
||||
api 'jakarta.mail:jakarta.mail-api:2.1.4'
|
||||
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.4'
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import stirling.software.common.model.oauth2.GitHubProvider;
|
||||
import stirling.software.common.model.oauth2.GoogleProvider;
|
||||
import stirling.software.common.model.oauth2.KeycloakProvider;
|
||||
import stirling.software.common.model.oauth2.Provider;
|
||||
import stirling.software.common.service.SsrfProtectionService.SsrfProtectionLevel;
|
||||
import stirling.software.common.util.ValidationUtils;
|
||||
|
||||
@Data
|
||||
@ -328,12 +329,18 @@ public class ApplicationProperties {
|
||||
private CustomPaths customPaths = new CustomPaths();
|
||||
private String fileUploadLimit;
|
||||
private TempFileManagement tempFileManagement = new TempFileManagement();
|
||||
private DatabaseBackup databaseBackup = new DatabaseBackup();
|
||||
|
||||
public boolean isAnalyticsEnabled() {
|
||||
return this.getEnableAnalytics() != null && this.getEnableAnalytics();
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DatabaseBackup {
|
||||
private String cron = "0 0 0 * * ?"; // daily at midnight
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class CustomPaths {
|
||||
private Pipeline pipeline = new Pipeline();
|
||||
@ -390,7 +397,7 @@ public class ApplicationProperties {
|
||||
@Data
|
||||
public static class UrlSecurity {
|
||||
private boolean enabled = true;
|
||||
private String level = "MEDIUM"; // MAX, MEDIUM, OFF
|
||||
private SsrfProtectionLevel level = SsrfProtectionLevel.MEDIUM; // MAX, MEDIUM, OFF
|
||||
private List<String> allowedDomains = new ArrayList<>();
|
||||
private List<String> blockedDomains = new ArrayList<>();
|
||||
private List<String> internalTlds =
|
||||
|
@ -7,14 +7,14 @@ import java.io.Reader;
|
||||
|
||||
import org.thymeleaf.templateresource.ITemplateResource;
|
||||
|
||||
public class InputStreamTemplateResource implements ITemplateResource {
|
||||
private InputStream inputStream;
|
||||
private String characterEncoding;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
public InputStreamTemplateResource(InputStream inputStream, String characterEncoding) {
|
||||
this.inputStream = inputStream;
|
||||
this.characterEncoding = characterEncoding;
|
||||
}
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class InputStreamTemplateResource implements ITemplateResource {
|
||||
private final InputStream inputStream;
|
||||
private final String characterEncoding;
|
||||
|
||||
@Override
|
||||
public Reader reader() throws IOException {
|
||||
|
@ -61,9 +61,9 @@ public class SsrfProtectionService {
|
||||
};
|
||||
}
|
||||
|
||||
private SsrfProtectionLevel parseProtectionLevel(String level) {
|
||||
private SsrfProtectionLevel parseProtectionLevel(SsrfProtectionLevel level) {
|
||||
try {
|
||||
return SsrfProtectionLevel.valueOf(level.toUpperCase());
|
||||
return SsrfProtectionLevel.valueOf(level.name());
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Invalid SSRF protection level '{}', defaulting to MEDIUM", level);
|
||||
return SsrfProtectionLevel.MEDIUM;
|
||||
@ -215,7 +215,8 @@ public class SsrfProtectionService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// For IPv4-mapped IPv6 addresses, bytes 10 and 11 must be 0xff (i.e., address is ::ffff:w.x.y.z)
|
||||
// For IPv4-mapped IPv6 addresses, bytes 10 and 11 must be 0xff (i.e., address is
|
||||
// ::ffff:w.x.y.z)
|
||||
return addr[10] == (byte) 0xff && addr[11] == (byte) 0xff;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,301 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.zip.Adler32;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class ChecksumUtils {
|
||||
|
||||
/** Shared buffer size for streaming I/O. */
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
/** Mask to extract the lower 32 bits of a long value (unsigned int). */
|
||||
private static final long UNSIGNED_32_BIT_MASK = 0xFFFFFFFFL;
|
||||
|
||||
/**
|
||||
* Computes a checksum for the given file using the chosen algorithm and returns a lowercase hex
|
||||
* string.
|
||||
*
|
||||
* <p>For digest algorithms (e.g., SHA-256, SHA-1, MD5), this returns the digest as hex. For
|
||||
* 32-bit {@link Checksum} algorithms ("CRC32", "ADLER32"), this returns an 8-character
|
||||
* lowercase hex string of the unsigned 32-bit value.
|
||||
*
|
||||
* @param path file to read
|
||||
* @param algorithm algorithm name (case-insensitive). Special values: "CRC32", "ADLER32".
|
||||
* @return hex string of the checksum
|
||||
* @throws IOException if the file cannot be read
|
||||
*/
|
||||
public static String checksum(Path path, String algorithm) throws IOException {
|
||||
try (InputStream is = Files.newInputStream(path)) {
|
||||
return checksum(is, algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a checksum for the given stream using the chosen algorithm and returns a lowercase
|
||||
* hex string.
|
||||
*
|
||||
* <p><strong>Note:</strong> This method does <em>not</em> close the provided stream.
|
||||
*
|
||||
* @param is input stream (not closed by this method)
|
||||
* @param algorithm algorithm name (case-insensitive). Special values: "CRC32", "ADLER32".
|
||||
* @return hex string of the checksum
|
||||
* @throws IOException if reading from the stream fails
|
||||
*/
|
||||
public static String checksum(InputStream is, String algorithm) throws IOException {
|
||||
switch (algorithm.toUpperCase(Locale.ROOT)) {
|
||||
case "CRC32":
|
||||
return checksumChecksum(is, new CRC32());
|
||||
case "ADLER32":
|
||||
return checksumChecksum(is, new Adler32());
|
||||
default:
|
||||
return toHex(checksumBytes(is, algorithm));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a checksum for the given file using the chosen algorithm and returns a Base64
|
||||
* encoded string.
|
||||
*
|
||||
* <p>For digest algorithms this is the Base64 of the raw digest bytes. For 32-bit checksum
|
||||
* algorithms ("CRC32", "ADLER32"), this is the Base64 of the 4-byte big-endian unsigned value.
|
||||
*
|
||||
* @param path file to read
|
||||
* @param algorithm algorithm name (case-insensitive). Special values: "CRC32", "ADLER32".
|
||||
* @return Base64-encoded checksum bytes
|
||||
* @throws IOException if the file cannot be read
|
||||
*/
|
||||
public static String checksumBase64(Path path, String algorithm) throws IOException {
|
||||
try (InputStream is = Files.newInputStream(path)) {
|
||||
return checksumBase64(is, algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a checksum for the given stream using the chosen algorithm and returns a Base64
|
||||
* encoded string.
|
||||
*
|
||||
* <p><strong>Note:</strong> This method does <em>not</em> close the provided stream.
|
||||
*
|
||||
* @param is input stream (not closed by this method)
|
||||
* @param algorithm algorithm name (case-insensitive). Special values: "CRC32", "ADLER32".
|
||||
* @return Base64-encoded checksum bytes
|
||||
* @throws IOException if reading from the stream fails
|
||||
*/
|
||||
public static String checksumBase64(InputStream is, String algorithm) throws IOException {
|
||||
switch (algorithm.toUpperCase(Locale.ROOT)) {
|
||||
case "CRC32":
|
||||
return Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new CRC32()));
|
||||
case "ADLER32":
|
||||
return Base64.getEncoder().encodeToString(checksumChecksumBytes(is, new Adler32()));
|
||||
default:
|
||||
return Base64.getEncoder().encodeToString(checksumBytes(is, algorithm));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes multiple checksums for the given file in a single pass over the data.
|
||||
*
|
||||
* <p>Returns a map from algorithm name to lowercase hex string. Order of results follows the
|
||||
* order of the provided {@code algorithms}.
|
||||
*
|
||||
* @param path file to read
|
||||
* @param algorithms algorithm names (case-insensitive). Special: "CRC32", "ADLER32".
|
||||
* @return map of algorithm → hex string
|
||||
* @throws IOException if the file cannot be read
|
||||
*/
|
||||
public static Map<String, String> checksums(Path path, String... algorithms)
|
||||
throws IOException {
|
||||
try (InputStream is = Files.newInputStream(path)) {
|
||||
return checksums(is, algorithms);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes multiple checksums for the given stream in a single pass over the data.
|
||||
*
|
||||
* <p><strong>Note:</strong> This method does <em>not</em> close the provided stream.
|
||||
*
|
||||
* @param is input stream (not closed by this method)
|
||||
* @param algorithms algorithm names (case-insensitive). Special: "CRC32", "ADLER32".
|
||||
* @return map of algorithm → hex string
|
||||
* @throws IOException if reading from the stream fails
|
||||
*/
|
||||
public static Map<String, String> checksums(InputStream is, String... algorithms)
|
||||
throws IOException {
|
||||
// Use LinkedHashMap to preserve the order of requested algorithms in the result.
|
||||
Map<String, MessageDigest> digests = new LinkedHashMap<>();
|
||||
Map<String, Checksum> checksums = new LinkedHashMap<>();
|
||||
|
||||
for (String algorithm : algorithms) {
|
||||
String key = algorithm; // keep original key for output
|
||||
switch (algorithm.toUpperCase(Locale.ROOT)) {
|
||||
case "CRC32":
|
||||
checksums.put(key, new CRC32());
|
||||
break;
|
||||
case "ADLER32":
|
||||
checksums.put(key, new Adler32());
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
// For MessageDigest, pass the original name (case-insensitive per JCA)
|
||||
digests.put(key, MessageDigest.getInstance(algorithm));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Unsupported algorithm: " + algorithm, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int read;
|
||||
while ((read = is.read(buffer)) != -1) {
|
||||
for (MessageDigest digest : digests.values()) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
for (Checksum cs : checksums.values()) {
|
||||
cs.update(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> results = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, MessageDigest> entry : digests.entrySet()) {
|
||||
results.put(entry.getKey(), toHex(entry.getValue().digest()));
|
||||
}
|
||||
for (Map.Entry<String, Checksum> entry : checksums.entrySet()) {
|
||||
// Keep value as long and mask to ensure unsigned hex formatting.
|
||||
long unsigned32 = entry.getValue().getValue() & UNSIGNED_32_BIT_MASK;
|
||||
results.put(entry.getKey(), String.format("%08x", unsigned32));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the checksum of a file with an expected hex string (case-insensitive).
|
||||
*
|
||||
* @param path file to read
|
||||
* @param algorithm algorithm name (case-insensitive). Special: "CRC32", "ADLER32".
|
||||
* @param expected expected hex string (case-insensitive)
|
||||
* @return {@code true} if they match, otherwise {@code false}
|
||||
* @throws IOException if the file cannot be read
|
||||
*/
|
||||
public static boolean matches(Path path, String algorithm, String expected) throws IOException {
|
||||
try (InputStream is = Files.newInputStream(path)) {
|
||||
return matches(is, algorithm, expected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the checksum of a stream with an expected hex string (case-insensitive).
|
||||
*
|
||||
* <p><strong>Note:</strong> This method does <em>not</em> close the provided stream.
|
||||
*
|
||||
* @param is input stream (not closed by this method)
|
||||
* @param algorithm algorithm name (case-insensitive). Special: "CRC32", "ADLER32".
|
||||
* @param expected expected hex string (case-insensitive)
|
||||
* @return {@code true} if they match, otherwise {@code false}
|
||||
* @throws IOException if reading from the stream fails
|
||||
*/
|
||||
public static boolean matches(InputStream is, String algorithm, String expected)
|
||||
throws IOException {
|
||||
return checksum(is, algorithm).equalsIgnoreCase(expected);
|
||||
}
|
||||
|
||||
// ---------- Internal helpers ----------
|
||||
|
||||
/**
|
||||
* Computes a MessageDigest over a stream and returns the raw digest bytes.
|
||||
*
|
||||
* @param is input stream (not closed)
|
||||
* @param algorithm JCA MessageDigest algorithm (e.g., "SHA-256")
|
||||
* @return raw digest bytes
|
||||
* @throws IOException if reading fails
|
||||
* @throws IllegalStateException if the algorithm is unsupported
|
||||
*/
|
||||
private static byte[] checksumBytes(InputStream is, String algorithm) throws IOException {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int read;
|
||||
while ((read = is.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
return digest.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Keep the message explicit to aid debugging
|
||||
throw new IllegalStateException("Unsupported algorithm: " + algorithm, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a 32-bit {@link Checksum} over a stream and returns the lowercase 8-char hex of the
|
||||
* unsigned 32-bit value.
|
||||
*
|
||||
* @param is input stream (not closed)
|
||||
* @param checksum checksum implementation (CRC32, Adler32, etc.)
|
||||
* @return 8-character lowercase hex (big-endian representation)
|
||||
* @throws IOException if reading fails
|
||||
*/
|
||||
private static String checksumChecksum(InputStream is, Checksum checksum) throws IOException {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int read;
|
||||
while ((read = is.read(buffer)) != -1) {
|
||||
checksum.update(buffer, 0, read);
|
||||
}
|
||||
// Keep as long and mask to ensure correct unsigned representation.
|
||||
long unsigned32 = checksum.getValue() & UNSIGNED_32_BIT_MASK;
|
||||
return String.format("%08x", unsigned32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a 32-bit {@link Checksum} over a stream and returns the raw 4-byte big-endian
|
||||
* representation of the unsigned 32-bit value.
|
||||
*
|
||||
* <p>Cast to int already truncates to the lower 32 bits; the sign is irrelevant because we
|
||||
* serialize the bit pattern directly into 4 bytes.
|
||||
*
|
||||
* @param is input stream (not closed)
|
||||
* @param checksum checksum implementation (CRC32, Adler32, etc.)
|
||||
* @return 4 bytes (big-endian)
|
||||
* @throws IOException if reading fails
|
||||
*/
|
||||
private static byte[] checksumChecksumBytes(InputStream is, Checksum checksum)
|
||||
throws IOException {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int read;
|
||||
while ((read = is.read(buffer)) != -1) {
|
||||
checksum.update(buffer, 0, read);
|
||||
}
|
||||
// Cast keeps only the lower 32 bits; mask is unnecessary here.
|
||||
int v = (int) checksum.getValue();
|
||||
return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(v).array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes to a lowercase hex string.
|
||||
*
|
||||
* @param hash the byte array to convert
|
||||
* @return the lowercase hex string
|
||||
*/
|
||||
private static String toHex(byte[] hash) {
|
||||
StringBuilder sb = new StringBuilder(hash.length * 2);
|
||||
for (byte b : hash) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -142,7 +142,8 @@ public class PdfUtils {
|
||||
ImageType colorType,
|
||||
boolean singleImage,
|
||||
int DPI,
|
||||
String filename)
|
||||
String filename,
|
||||
boolean includeAnnotations)
|
||||
throws IOException, Exception {
|
||||
|
||||
// Validate and limit DPI to prevent excessive memory usage
|
||||
@ -163,6 +164,9 @@ public class PdfUtils {
|
||||
try (PDDocument document = pdfDocumentFactory.load(inputStream)) {
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
pdfRenderer.setSubsamplingAllowed(true);
|
||||
if (!includeAnnotations) {
|
||||
pdfRenderer.setAnnotationsFilter(annotation -> false);
|
||||
}
|
||||
int pageCount = document.getNumberOfPages();
|
||||
|
||||
// Create a ByteArrayOutputStream to save the image(s) to
|
||||
|
@ -15,6 +15,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.github.pixee.security.BoundedLineReader;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
@ -303,6 +305,8 @@ public class ProcessExecutor {
|
||||
OCR_MY_PDF
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public class ProcessExecutorResult {
|
||||
int rc;
|
||||
String messages;
|
||||
@ -312,20 +316,5 @@ public class ProcessExecutor {
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public int getRc() {
|
||||
return rc;
|
||||
}
|
||||
|
||||
public void setRc(int rc) {
|
||||
this.rc = rc;
|
||||
}
|
||||
|
||||
public String getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void setMessages(String messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
@ -14,6 +15,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class TempFile implements AutoCloseable {
|
||||
|
||||
private final TempFileManager manager;
|
||||
@Getter
|
||||
private final File file;
|
||||
|
||||
public TempFile(TempFileManager manager, String suffix) throws IOException {
|
||||
@ -21,10 +23,6 @@ public class TempFile implements AutoCloseable {
|
||||
this.file = manager.createTempFile(suffix);
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return file.toPath();
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -24,8 +25,22 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class TempFileRegistry {
|
||||
|
||||
private final ConcurrentMap<Path, Instant> registeredFiles = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* -- GETTER --
|
||||
* Get all registered third-party temporary files.
|
||||
*
|
||||
* @return Set of third-party file paths
|
||||
*/
|
||||
@Getter
|
||||
private final Set<Path> thirdPartyTempFiles =
|
||||
Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
/**
|
||||
* -- GETTER --
|
||||
* Get all registered temporary directories.
|
||||
*
|
||||
* @return Set of temporary directory paths
|
||||
*/
|
||||
@Getter
|
||||
private final Set<Path> tempDirectories = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
/**
|
||||
@ -133,24 +148,6 @@ public class TempFileRegistry {
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered third-party temporary files.
|
||||
*
|
||||
* @return Set of third-party file paths
|
||||
*/
|
||||
public Set<Path> getThirdPartyTempFiles() {
|
||||
return thirdPartyTempFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered temporary directories.
|
||||
*
|
||||
* @return Set of temporary directory paths
|
||||
*/
|
||||
public Set<Path> getTempDirectories() {
|
||||
return tempDirectories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is registered in the registry.
|
||||
*
|
||||
|
@ -0,0 +1,111 @@
|
||||
package stirling.software.common.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
public class FileInfoTest {
|
||||
|
||||
@ParameterizedTest(name = "{index}: fileSize={0}")
|
||||
@CsvSource({
|
||||
"0, '0 Bytes'",
|
||||
"1023, '1023 Bytes'",
|
||||
"1024, '1.00 KB'",
|
||||
"1048575, '1024.00 KB'", // Do we really want this as result?
|
||||
"1048576, '1.00 MB'",
|
||||
"1073741823, '1024.00 MB'", // Do we really want this as result?
|
||||
"1073741824, '1.00 GB'"
|
||||
})
|
||||
void testGetFormattedFileSize(long fileSize, String expectedFormattedSize) {
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
File.separator
|
||||
+ "path"
|
||||
+ File.separator
|
||||
+ "to"
|
||||
+ File.separator
|
||||
+ "example.txt",
|
||||
LocalDateTime.now(),
|
||||
fileSize,
|
||||
LocalDateTime.now().minusDays(1));
|
||||
|
||||
assertEquals(expectedFormattedSize, fileInfo.getFormattedFileSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFilePathAsPath() {
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"test.pdf",
|
||||
File.separator + "tmp" + File.separator + "test.pdf",
|
||||
LocalDateTime.now(),
|
||||
1234,
|
||||
LocalDateTime.now().minusDays(2));
|
||||
assertEquals(
|
||||
File.separator + "tmp" + File.separator + "test.pdf",
|
||||
fileInfo.getFilePathAsPath().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFormattedModificationDate() {
|
||||
LocalDateTime modDate = LocalDateTime.of(2024, 6, 1, 15, 30, 45);
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"file.txt",
|
||||
File.separator + "file.txt",
|
||||
modDate,
|
||||
100,
|
||||
LocalDateTime.of(2024, 5, 31, 10, 0, 0));
|
||||
assertEquals("2024-06-01 15:30:45", fileInfo.getFormattedModificationDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFormattedCreationDate() {
|
||||
LocalDateTime creationDate = LocalDateTime.of(2023, 12, 25, 8, 15, 0);
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"holiday.txt",
|
||||
File.separator + "holiday.txt",
|
||||
LocalDateTime.of(2024, 1, 1, 0, 0, 0),
|
||||
500,
|
||||
creationDate);
|
||||
assertEquals("2023-12-25 08:15:00", fileInfo.getFormattedCreationDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGettersAndSetters() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"doc.pdf",
|
||||
File.separator + "docs" + File.separator + "doc.pdf",
|
||||
now,
|
||||
2048,
|
||||
now.minusDays(1));
|
||||
// Test getters
|
||||
assertEquals("doc.pdf", fileInfo.getFileName());
|
||||
assertEquals(File.separator + "docs" + File.separator + "doc.pdf", fileInfo.getFilePath());
|
||||
assertEquals(now, fileInfo.getModificationDate());
|
||||
assertEquals(2048, fileInfo.getFileSize());
|
||||
assertEquals(now.minusDays(1), fileInfo.getCreationDate());
|
||||
|
||||
// Test setters
|
||||
fileInfo.setFileName("new.pdf");
|
||||
fileInfo.setFilePath(File.separator + "new" + File.separator + "new.pdf");
|
||||
fileInfo.setModificationDate(now.plusDays(1));
|
||||
fileInfo.setFileSize(4096);
|
||||
fileInfo.setCreationDate(now.minusDays(2));
|
||||
|
||||
assertEquals("new.pdf", fileInfo.getFileName());
|
||||
assertEquals(File.separator + "new" + File.separator + "new.pdf", fileInfo.getFilePath());
|
||||
assertEquals(now.plusDays(1), fileInfo.getModificationDate());
|
||||
assertEquals(4096, fileInfo.getFileSize());
|
||||
assertEquals(now.minusDays(2), fileInfo.getCreationDate());
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package stirling.software.common.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class InputStreamTemplateResourceTest {
|
||||
|
||||
@Test
|
||||
void gettersReturnProvidedFields() {
|
||||
byte[] data = {1, 2, 3};
|
||||
InputStream is = new ByteArrayInputStream(data);
|
||||
String encoding = "UTF-8";
|
||||
InputStreamTemplateResource resource = new InputStreamTemplateResource(is, encoding);
|
||||
|
||||
assertSame(is, resource.getInputStream());
|
||||
assertEquals(encoding, resource.getCharacterEncoding());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldsAreFinal() throws NoSuchFieldException {
|
||||
Field inputStreamField = InputStreamTemplateResource.class.getDeclaredField("inputStream");
|
||||
Field characterEncodingField =
|
||||
InputStreamTemplateResource.class.getDeclaredField("characterEncoding");
|
||||
|
||||
assertTrue(Modifier.isFinal(inputStreamField.getModifiers()));
|
||||
assertTrue(Modifier.isFinal(characterEncodingField.getModifiers()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void noSetterMethodsPresent() {
|
||||
long setterCount =
|
||||
Arrays.stream(InputStreamTemplateResource.class.getDeclaredMethods())
|
||||
.filter(method -> method.getName().startsWith("set"))
|
||||
.count();
|
||||
|
||||
assertEquals(0, setterCount, "InputStreamTemplateResource should not have setter methods");
|
||||
}
|
||||
|
||||
@Test
|
||||
void readerReturnsCorrectContent() throws Exception {
|
||||
String content = "Hello, world!";
|
||||
InputStream is = new ByteArrayInputStream(content.getBytes("UTF-8"));
|
||||
InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8");
|
||||
|
||||
try (Reader reader = resource.reader()) {
|
||||
char[] buffer = new char[content.length()];
|
||||
int read = reader.read(buffer);
|
||||
assertEquals(content.length(), read);
|
||||
assertEquals(content, new String(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void relativeThrowsUnsupportedOperationException() {
|
||||
InputStream is = new ByteArrayInputStream(new byte[0]);
|
||||
InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8");
|
||||
assertThrows(UnsupportedOperationException.class, () -> resource.relative("other"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDescriptionReturnsExpectedString() {
|
||||
InputStream is = new ByteArrayInputStream(new byte[0]);
|
||||
InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8");
|
||||
assertEquals("InputStream resource [Stream]", resource.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBaseNameReturnsExpectedString() {
|
||||
InputStream is = new ByteArrayInputStream(new byte[0]);
|
||||
InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8");
|
||||
assertEquals("streamResource", resource.getBaseName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsReturnsTrueWhenInputStreamNotNull() {
|
||||
InputStream is = new ByteArrayInputStream(new byte[0]);
|
||||
InputStreamTemplateResource resource = new InputStreamTemplateResource(is, "UTF-8");
|
||||
assertTrue(resource.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsReturnsFalseWhenInputStreamIsNull() {
|
||||
InputStreamTemplateResource resource = new InputStreamTemplateResource(null, "UTF-8");
|
||||
assertFalse(resource.exists());
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ChecksumUtilsTest {
|
||||
|
||||
@Test
|
||||
void computeChecksums_basic() throws Exception {
|
||||
byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// MD5 (hex)
|
||||
try (InputStream is = new ByteArrayInputStream(data)) {
|
||||
assertEquals("5d41402abc4b2a76b9719d911017c592", ChecksumUtils.checksum(is, "MD5"));
|
||||
}
|
||||
|
||||
// MD5 (Base64)
|
||||
try (InputStream is = new ByteArrayInputStream(data)) {
|
||||
assertEquals("XUFAKrxLKna5cZ2REBfFkg==", ChecksumUtils.checksumBase64(is, "MD5"));
|
||||
}
|
||||
|
||||
// MD5 + CRC32 (hex map)
|
||||
try (InputStream is = new ByteArrayInputStream(data)) {
|
||||
Map<String, String> map = ChecksumUtils.checksums(is, "MD5", "CRC32");
|
||||
assertEquals("5d41402abc4b2a76b9719d911017c592", map.get("MD5"));
|
||||
assertEquals("3610a686", map.get("CRC32"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void crc32_base64_bigEndianBytes_forHello() throws Exception {
|
||||
// CRC32("hello") = 0x3610A686 → bytes: 36 10 A6 86 → Base64: "NhCmhg=="
|
||||
byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
|
||||
try (InputStream is = new ByteArrayInputStream(data)) {
|
||||
assertEquals("NhCmhg==", ChecksumUtils.checksumBase64(is, "CRC32"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void crc32_unsignedFormatting_highBitSet() throws Exception {
|
||||
// CRC32 of single zero byte (0x00) is 0xD202EF8D (>= 0x8000_0000)
|
||||
byte[] data = new byte[] {0x00};
|
||||
|
||||
// Hex (unsigned, 8 chars, lowercase)
|
||||
try (InputStream is = new ByteArrayInputStream(data)) {
|
||||
assertEquals("d202ef8d", ChecksumUtils.checksum(is, "CRC32"));
|
||||
}
|
||||
|
||||
// Base64 of the 4-byte big-endian representation
|
||||
try (InputStream is = new ByteArrayInputStream(data)) {
|
||||
assertEquals("0gLvjQ==", ChecksumUtils.checksumBase64(is, "CRC32"));
|
||||
}
|
||||
|
||||
// matches(..) must be case-insensitive for hex
|
||||
try (InputStream is = new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8))) {
|
||||
assertTrue(ChecksumUtils.matches(is, "CRC32", "3610A686")); // uppercase expected
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import stirling.software.common.model.FileInfo;
|
||||
|
||||
public class FileInfoTest {
|
||||
|
||||
@ParameterizedTest(name = "{index}: fileSize={0}")
|
||||
@CsvSource({
|
||||
"0, '0 Bytes'",
|
||||
"1023, '1023 Bytes'",
|
||||
"1024, '1.00 KB'",
|
||||
"1048575, '1024.00 KB'", // Do we really want this as result?
|
||||
"1048576, '1.00 MB'",
|
||||
"1073741823, '1024.00 MB'", // Do we really want this as result?
|
||||
"1073741824, '1.00 GB'"
|
||||
})
|
||||
void testGetFormattedFileSize(long fileSize, String expectedFormattedSize) {
|
||||
FileInfo fileInfo =
|
||||
new FileInfo(
|
||||
"example.txt",
|
||||
"/path/to/example.txt",
|
||||
LocalDateTime.now(),
|
||||
fileSize,
|
||||
LocalDateTime.now().minusDays(1));
|
||||
|
||||
assertEquals(expectedFormattedSize, fileInfo.getFormattedFileSize());
|
||||
}
|
||||
}
|
4
app/core/.gitignore
vendored
4
app/core/.gitignore
vendored
@ -170,6 +170,10 @@ out/
|
||||
*.jks
|
||||
*.asc
|
||||
|
||||
# test-cert
|
||||
!**/test/resources/certs/test-cert.*
|
||||
!**/test/resources/certs/test-key.*
|
||||
|
||||
# SSH Keys
|
||||
*.pub
|
||||
*.priv
|
||||
|
@ -31,6 +31,8 @@ import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
|
||||
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
||||
import org.bouncycastle.cms.CMSException;
|
||||
@ -44,8 +46,21 @@ import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
||||
|
||||
public abstract class CreateSignatureBase implements SignatureInterface {
|
||||
private PrivateKey privateKey;
|
||||
@Getter
|
||||
private Certificate[] certificateChain;
|
||||
@Setter
|
||||
private String tsaUrl;
|
||||
/**
|
||||
* Specifies whether the external signing scenario should be used.
|
||||
* If set to {@code true}, external signing will be performed and
|
||||
* {@link SignatureInterface} will be used for signing.
|
||||
* If set to {@code false}, internal signing will be performed.
|
||||
* <p>Default: {@code false}
|
||||
*
|
||||
* @param externalSigning {@code true} if external signing should be performed; {@code false} for internal signing
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
private boolean externalSigning;
|
||||
|
||||
/**
|
||||
@ -97,18 +112,10 @@ public abstract class CreateSignatureBase implements SignatureInterface {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public Certificate[] getCertificateChain() {
|
||||
return certificateChain;
|
||||
}
|
||||
|
||||
public final void setCertificateChain(final Certificate[] certificateChain) {
|
||||
this.certificateChain = certificateChain;
|
||||
}
|
||||
|
||||
public void setTsaUrl(String tsaUrl) {
|
||||
this.tsaUrl = tsaUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* SignatureInterface sample implementation.
|
||||
*
|
||||
@ -152,19 +159,4 @@ public abstract class CreateSignatureBase implements SignatureInterface {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isExternalSigning() {
|
||||
return externalSigning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if external signing scenario should be used. If {@code false}, SignatureInterface would
|
||||
* be used for signing.
|
||||
*
|
||||
* <p>Default: {@code false}
|
||||
*
|
||||
* @param externalSigning {@code true} if external signing should be performed
|
||||
*/
|
||||
public void setExternalSigning(boolean externalSigning) {
|
||||
this.externalSigning = externalSigning;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -19,6 +20,7 @@ public class EndpointConfiguration {
|
||||
|
||||
private static final String REMOVE_BLANKS = "remove-blanks";
|
||||
private final ApplicationProperties applicationProperties;
|
||||
@Getter
|
||||
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
|
||||
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
|
||||
private Set<String> disabledGroups = new HashSet<>();
|
||||
@ -46,10 +48,6 @@ public class EndpointConfiguration {
|
||||
endpointStatuses.put(endpoint, false);
|
||||
}
|
||||
|
||||
public Map<String, Boolean> getEndpointStatuses() {
|
||||
return endpointStatuses;
|
||||
}
|
||||
|
||||
public boolean isEndpointEnabled(String endpoint) {
|
||||
String original = endpoint;
|
||||
if (endpoint.startsWith("/")) {
|
||||
|
@ -6,6 +6,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
|
||||
@ -234,33 +236,12 @@ public class EditTableOfContentsController {
|
||||
}
|
||||
|
||||
// Inner class to represent bookmarks in JSON
|
||||
@Setter
|
||||
@Getter
|
||||
public static class BookmarkItem {
|
||||
private String title;
|
||||
private int pageNumber;
|
||||
private List<BookmarkItem> children = new ArrayList<>();
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public int getPageNumber() {
|
||||
return pageNumber;
|
||||
}
|
||||
|
||||
public void setPageNumber(int pageNumber) {
|
||||
this.pageNumber = pageNumber;
|
||||
}
|
||||
|
||||
public List<BookmarkItem> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public void setChildren(List<BookmarkItem> children) {
|
||||
this.children = children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ public class ConvertImgPDFController {
|
||||
String colorType = request.getColorType();
|
||||
int dpi = request.getDpi();
|
||||
String pageNumbers = request.getPageNumbers();
|
||||
boolean includeAnnotations = Boolean.TRUE.equals(request.getIncludeAnnotations());
|
||||
Path tempFile = null;
|
||||
Path tempOutputDir = null;
|
||||
Path tempPdfPath = null;
|
||||
@ -101,7 +102,8 @@ public class ConvertImgPDFController {
|
||||
colorTypeResult,
|
||||
singleImage,
|
||||
dpi,
|
||||
filename);
|
||||
filename,
|
||||
includeAnnotations);
|
||||
if (result == null || result.length == 0) {
|
||||
log.error("resultant bytes for {} is null, error converting ", filename);
|
||||
}
|
||||
|
@ -5,10 +5,11 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -23,7 +24,9 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.common.model.api.GeneralFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
@ -36,59 +39,130 @@ import stirling.software.common.util.WebResponseUtils;
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@RequestMapping("/api/v1/convert")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ConvertOfficeController {
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final RuntimePathConfig runtimePathConfig;
|
||||
private final CustomHtmlSanitizer customHtmlSanitizer;
|
||||
private final EndpointConfiguration endpointConfiguration;
|
||||
|
||||
public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||
// Check for valid file extension
|
||||
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||
if (originalFilename == null
|
||||
|| !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) {
|
||||
throw new IllegalArgumentException("Invalid file extension");
|
||||
private boolean isUnoconvertAvailable() {
|
||||
return endpointConfiguration.isGroupEnabled("Unoconvert")
|
||||
|| endpointConfiguration.isGroupEnabled("Python");
|
||||
}
|
||||
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile =
|
||||
Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
|
||||
public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
|
||||
// Check for valid file extension and sanitize filename
|
||||
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||
if (originalFilename == null || originalFilename.isBlank()) {
|
||||
throw new IllegalArgumentException("Missing original filename");
|
||||
}
|
||||
|
||||
// Check for valid file extension
|
||||
String extension = FilenameUtils.getExtension(originalFilename);
|
||||
if (extension == null || !isValidFileExtension(extension)) {
|
||||
throw new IllegalArgumentException("Invalid file extension");
|
||||
}
|
||||
String extensionLower = extension.toLowerCase();
|
||||
|
||||
String baseName = FilenameUtils.getBaseName(originalFilename);
|
||||
if (baseName == null || baseName.isBlank()) {
|
||||
baseName = "input";
|
||||
}
|
||||
|
||||
// create temporary working directory
|
||||
Path workDir = Files.createTempDirectory("office2pdf_");
|
||||
Path inputPath = workDir.resolve(baseName + "." + extensionLower);
|
||||
Path outputPath = workDir.resolve(baseName + ".pdf");
|
||||
|
||||
// Check if the file is HTML and apply sanitization if needed
|
||||
String fileExtension = FilenameUtils.getExtension(originalFilename).toLowerCase();
|
||||
if ("html".equals(fileExtension) || "htm".equals(fileExtension)) {
|
||||
if ("html".equals(extensionLower) || "htm".equals(extensionLower)) {
|
||||
// Read and sanitize HTML content
|
||||
String htmlContent = new String(inputFile.getBytes(), StandardCharsets.UTF_8);
|
||||
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlContent);
|
||||
Files.write(tempInputFile, sanitizedHtml.getBytes(StandardCharsets.UTF_8));
|
||||
Files.writeString(inputPath, sanitizedHtml, StandardCharsets.UTF_8);
|
||||
} else {
|
||||
inputFile.transferTo(tempInputFile);
|
||||
// copy file content
|
||||
Files.copy(inputFile.getInputStream(), inputPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
// Prepare the output file path
|
||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||
|
||||
try {
|
||||
// Run the LibreOffice command
|
||||
List<String> command =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
runtimePathConfig.getUnoConvertPath(),
|
||||
"--port",
|
||||
"2003",
|
||||
"--convert-to",
|
||||
"pdf",
|
||||
tempInputFile.toString(),
|
||||
tempOutputFile.toString()));
|
||||
ProcessExecutorResult returnCode =
|
||||
ProcessExecutorResult result;
|
||||
// Run Unoconvert command
|
||||
if (isUnoconvertAvailable()) {
|
||||
// Unoconvert: schreibe direkt in outputPath innerhalb des workDir
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(runtimePathConfig.getUnoConvertPath());
|
||||
command.add("--port");
|
||||
command.add("2003");
|
||||
command.add("--convert-to");
|
||||
command.add("pdf");
|
||||
command.add(inputPath.toString());
|
||||
command.add(outputPath.toString());
|
||||
|
||||
result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
} // Run soffice command
|
||||
else {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("soffice");
|
||||
command.add("--headless");
|
||||
command.add("--nologo");
|
||||
command.add("--convert-to");
|
||||
command.add("pdf:writer_pdf_Export");
|
||||
command.add("--outdir");
|
||||
command.add(workDir.toString());
|
||||
command.add(inputPath.toString());
|
||||
|
||||
// Read the converted PDF file
|
||||
return tempOutputFile.toFile();
|
||||
result =
|
||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||
.runCommandWithOutputHandling(command);
|
||||
}
|
||||
|
||||
// Check the result
|
||||
if (result == null) {
|
||||
throw new IllegalStateException("Converter returned no result");
|
||||
}
|
||||
if (result.getRc() != 0) {
|
||||
throw new IllegalStateException("Conversion failed (exit " + result.getRc() + ")");
|
||||
}
|
||||
|
||||
if (!Files.exists(outputPath)) {
|
||||
// Some LibreOffice versions may deviate with exotic names – as a fallback, we try
|
||||
// to find any .pdf in the workDir
|
||||
try (var stream = Files.list(workDir)) {
|
||||
Path fallback =
|
||||
stream.filter(
|
||||
p ->
|
||||
p.getFileName()
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.endsWith(".pdf"))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (fallback == null) {
|
||||
throw new IllegalStateException("No PDF produced.");
|
||||
}
|
||||
// Move the found PDF to the expected outputPath
|
||||
Files.move(fallback, outputPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the output file is empty
|
||||
if (Files.size(outputPath) == 0L) {
|
||||
throw new IllegalStateException("Produced PDF is empty");
|
||||
}
|
||||
|
||||
return outputPath.toFile();
|
||||
} finally {
|
||||
// Clean up the temporary files
|
||||
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||
try {
|
||||
Files.deleteIfExists(inputPath);
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to delete temp input file: {}", inputPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +193,9 @@ public class ConvertOfficeController {
|
||||
.replaceFirst("[.][^.]+$", "")
|
||||
+ "_convertedToPDF.pdf");
|
||||
} finally {
|
||||
if (file != null) file.delete();
|
||||
if (file != null && file.getParent() != null) {
|
||||
FileUtils.deleteDirectory(file.getParentFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,21 @@
|
||||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
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.springframework.http.HttpStatus;
|
||||
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.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -23,7 +27,6 @@ import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.GeneralUtils;
|
||||
import stirling.software.common.util.ProcessExecutor;
|
||||
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
|
||||
@ -46,24 +49,43 @@ public class ConvertWebsiteToPDF {
|
||||
description =
|
||||
"This endpoint fetches content from a URL and converts it to a PDF format."
|
||||
+ " Input:N/A Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> urlToPdf(@ModelAttribute UrlToPdfRequest request)
|
||||
public ResponseEntity<?> urlToPdf(@ModelAttribute UrlToPdfRequest request)
|
||||
throws IOException, InterruptedException {
|
||||
String URL = request.getUrlInput();
|
||||
UriComponentsBuilder uriComponentsBuilder =
|
||||
ServletUriComponentsBuilder.fromCurrentContextPath().path("/url-to-pdf");
|
||||
URI location = null;
|
||||
HttpStatus status = HttpStatus.SEE_OTHER;
|
||||
|
||||
if (!applicationProperties.getSystem().getEnableUrlToPDF()) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.endpointDisabled", "This endpoint has been disabled by the admin");
|
||||
}
|
||||
location =
|
||||
uriComponentsBuilder
|
||||
.queryParam("error", "error.endpointDisabled")
|
||||
.build()
|
||||
.toUri();
|
||||
} else
|
||||
|
||||
// Validate the URL format
|
||||
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
|
||||
throw ExceptionUtils.createInvalidArgumentException(
|
||||
"URL", "provided format is invalid");
|
||||
}
|
||||
location =
|
||||
uriComponentsBuilder
|
||||
.queryParam("error", "error.invalidUrlFormat")
|
||||
.build()
|
||||
.toUri();
|
||||
} else
|
||||
|
||||
// validate the URL is reachable
|
||||
if (!GeneralUtils.isURLReachable(URL)) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.urlNotReachable", "URL is not reachable, please provide a valid URL");
|
||||
location =
|
||||
uriComponentsBuilder
|
||||
.queryParam("error", "error.urlNotReachable")
|
||||
.build()
|
||||
.toUri();
|
||||
}
|
||||
|
||||
if (location != null) {
|
||||
log.info("Redirecting to: {}", location.toString());
|
||||
return ResponseEntity.status(status).location(location).build();
|
||||
}
|
||||
|
||||
Path tempOutputFile = null;
|
||||
|
@ -1,8 +1,10 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
@ -54,24 +56,27 @@ public class PageNumbersController {
|
||||
String customText = request.getCustomText();
|
||||
float fontSize = request.getFontSize();
|
||||
String fontType = request.getFontType();
|
||||
String fontColor = request.getFontColor();
|
||||
|
||||
Color color = Color.BLACK;
|
||||
if (fontColor != null && !fontColor.trim().isEmpty()) {
|
||||
try {
|
||||
color = Color.decode(fontColor);
|
||||
} catch (NumberFormatException e) {
|
||||
color = Color.BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
PDDocument document = pdfDocumentFactory.load(file);
|
||||
float marginFactor;
|
||||
switch (customMargin.toLowerCase()) {
|
||||
case "small":
|
||||
marginFactor = 0.02f;
|
||||
break;
|
||||
case "large":
|
||||
marginFactor = 0.05f;
|
||||
break;
|
||||
case "x-large":
|
||||
marginFactor = 0.075f;
|
||||
break;
|
||||
case "medium":
|
||||
default:
|
||||
marginFactor = 0.035f;
|
||||
break;
|
||||
}
|
||||
|
||||
float marginFactor =
|
||||
switch (customMargin == null ? "" : customMargin.toLowerCase(Locale.ROOT)) {
|
||||
case "small" -> 0.02f;
|
||||
case "large" -> 0.05f;
|
||||
case "x-large" -> 0.075f;
|
||||
case "medium" -> 0.035f;
|
||||
default -> 0.035f;
|
||||
};
|
||||
|
||||
if (pagesToNumber == null || pagesToNumber.isEmpty()) {
|
||||
pagesToNumber = "all";
|
||||
@ -79,9 +84,17 @@ public class PageNumbersController {
|
||||
if (customText == null || customText.isEmpty()) {
|
||||
customText = "{n}";
|
||||
}
|
||||
|
||||
final String baseFilename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
|
||||
List<Integer> pagesToNumberList =
|
||||
GeneralUtils.parsePageList(pagesToNumber.split(","), document.getNumberOfPages());
|
||||
|
||||
// Clamp position to 1..9 (1 = top-left, 9 = bottom-right)
|
||||
int pos = Math.max(1, Math.min(9, position));
|
||||
|
||||
for (int i : pagesToNumberList) {
|
||||
PDPage page = document.getPage(i);
|
||||
PDRectangle pageSize = page.getMediaBox();
|
||||
@ -90,70 +103,62 @@ public class PageNumbersController {
|
||||
customText
|
||||
.replace("{n}", String.valueOf(pageNumber))
|
||||
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
||||
.replace(
|
||||
"{filename}",
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", ""));
|
||||
.replace("{filename}", baseFilename);
|
||||
|
||||
PDType1Font currentFont =
|
||||
switch (fontType.toLowerCase()) {
|
||||
switch (fontType == null ? "" : fontType.toLowerCase(Locale.ROOT)) {
|
||||
case "courier" -> new PDType1Font(Standard14Fonts.FontName.COURIER);
|
||||
case "times" -> new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN);
|
||||
default -> new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||
};
|
||||
|
||||
float x, y;
|
||||
// Text dimensions and font metrics
|
||||
float textWidth = currentFont.getStringWidth(text) / 1000f * fontSize;
|
||||
float ascent = currentFont.getFontDescriptor().getAscent() / 1000f * fontSize;
|
||||
float descent = currentFont.getFontDescriptor().getDescent() / 1000f * fontSize;
|
||||
|
||||
if (position == 5) {
|
||||
// Calculate text width and font metrics
|
||||
float textWidth = currentFont.getStringWidth(text) / 1000 * fontSize;
|
||||
// Derive column/row in range 1..3 (1 = left/top, 2 = center/middle, 3 = right/bottom)
|
||||
int col = ((pos - 1) % 3) + 1; // 1 = left, 2 = center, 3 = right
|
||||
int row = ((pos - 1) / 3) + 1; // 1 = top, 2 = middle, 3 = bottom
|
||||
|
||||
float ascent = currentFont.getFontDescriptor().getAscent() / 1000 * fontSize;
|
||||
float descent = currentFont.getFontDescriptor().getDescent() / 1000 * fontSize;
|
||||
// Anchor coordinates with margin
|
||||
float leftX = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
|
||||
float midX = pageSize.getLowerLeftX() + pageSize.getWidth() / 2f;
|
||||
float rightX = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();
|
||||
|
||||
float centerX = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);
|
||||
float centerY = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);
|
||||
float botY = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
|
||||
float midY = pageSize.getLowerLeftY() + pageSize.getHeight() / 2f;
|
||||
float topY = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();
|
||||
|
||||
x = centerX - (textWidth / 2);
|
||||
y = centerY - (ascent + descent) / 2;
|
||||
} else {
|
||||
int xGroup = (position - 1) % 3;
|
||||
int yGroup = 2 - (position - 1) / 3;
|
||||
|
||||
x =
|
||||
switch (xGroup) {
|
||||
case 0 ->
|
||||
pageSize.getLowerLeftX()
|
||||
+ marginFactor * pageSize.getWidth(); // left
|
||||
case 1 ->
|
||||
pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); // center
|
||||
default ->
|
||||
pageSize.getUpperRightX()
|
||||
- marginFactor * pageSize.getWidth(); // right
|
||||
// Horizontal alignment: left = anchor, center = centered, right = right-aligned
|
||||
float x =
|
||||
switch (col) {
|
||||
case 1 -> leftX;
|
||||
case 2 -> midX - textWidth / 2f;
|
||||
default -> rightX - textWidth;
|
||||
};
|
||||
|
||||
y =
|
||||
switch (yGroup) {
|
||||
case 0 ->
|
||||
pageSize.getLowerLeftY()
|
||||
+ marginFactor * pageSize.getHeight(); // bottom
|
||||
case 1 ->
|
||||
pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); // middle
|
||||
default ->
|
||||
pageSize.getUpperRightY()
|
||||
- marginFactor * pageSize.getHeight(); // top
|
||||
// Vertical alignment (baseline!):
|
||||
// top = align text top at topY,
|
||||
// middle = optical middle using ascent/descent,
|
||||
// bottom = baseline at botY
|
||||
float y =
|
||||
switch (row) {
|
||||
case 1 -> topY - ascent;
|
||||
case 2 -> midY - (ascent + descent) / 2f;
|
||||
default -> botY;
|
||||
};
|
||||
}
|
||||
|
||||
PDPageContentStream contentStream =
|
||||
try (PDPageContentStream contentStream =
|
||||
new PDPageContentStream(
|
||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||
document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
|
||||
contentStream.beginText();
|
||||
contentStream.setFont(currentFont, fontSize);
|
||||
contentStream.setNonStrokingColor(color);
|
||||
contentStream.newLineAtOffset(x, y);
|
||||
contentStream.showText(text);
|
||||
contentStream.endText();
|
||||
contentStream.close();
|
||||
}
|
||||
|
||||
pageNumber++;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.beans.PropertyEditorSupport;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -25,6 +26,8 @@ import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -52,6 +55,24 @@ public class StampController {
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
/**
|
||||
* Initialize data binder for multipart file uploads.
|
||||
* This method registers a custom editor for MultipartFile to handle file uploads.
|
||||
* It sets the MultipartFile to null if the uploaded file is empty.
|
||||
* This is necessary to avoid binding errors when the file is not present.
|
||||
*/
|
||||
@InitBinder
|
||||
public void initBinder(WebDataBinder binder) {
|
||||
binder.registerCustomEditor(
|
||||
MultipartFile.class,
|
||||
new PropertyEditorSupport() {
|
||||
@Override
|
||||
public void setAsText(String text) throws IllegalArgumentException {
|
||||
setValue(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
|
||||
@Operation(
|
||||
summary = "Add stamp to a PDF file",
|
||||
@ -91,25 +112,14 @@ public class StampController {
|
||||
float overrideY = request.getOverrideY(); // New field for Y override
|
||||
|
||||
String customColor = request.getCustomColor();
|
||||
float marginFactor;
|
||||
|
||||
float marginFactor =
|
||||
switch (request.getCustomMargin().toLowerCase()) {
|
||||
case "small":
|
||||
marginFactor = 0.02f;
|
||||
break;
|
||||
case "medium":
|
||||
marginFactor = 0.035f;
|
||||
break;
|
||||
case "large":
|
||||
marginFactor = 0.05f;
|
||||
break;
|
||||
case "x-large":
|
||||
marginFactor = 0.075f;
|
||||
break;
|
||||
default:
|
||||
marginFactor = 0.035f;
|
||||
break;
|
||||
}
|
||||
case "small" -> 0.02f;
|
||||
case "medium" -> 0.035f;
|
||||
case "large" -> 0.05f;
|
||||
case "x-large" -> 0.075f;
|
||||
default -> 0.035f;
|
||||
};
|
||||
|
||||
// Load the input PDF
|
||||
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
||||
@ -185,27 +195,16 @@ public class StampController {
|
||||
throws IOException {
|
||||
String resourceDir = "";
|
||||
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||
resourceDir =
|
||||
switch (alphabet) {
|
||||
case "arabic":
|
||||
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
|
||||
break;
|
||||
case "japanese":
|
||||
resourceDir = "static/fonts/Meiryo.ttf";
|
||||
break;
|
||||
case "korean":
|
||||
resourceDir = "static/fonts/malgun.ttf";
|
||||
break;
|
||||
case "chinese":
|
||||
resourceDir = "static/fonts/SimSun.ttf";
|
||||
break;
|
||||
case "thai":
|
||||
resourceDir = "static/fonts/NotoSansThai-Regular.ttf";
|
||||
break;
|
||||
case "roman":
|
||||
default:
|
||||
resourceDir = "static/fonts/NotoSans-Regular.ttf";
|
||||
break;
|
||||
}
|
||||
case "arabic" -> "static/fonts/NotoSansArabic-Regular.ttf";
|
||||
case "japanese" -> "static/fonts/Meiryo.ttf";
|
||||
case "korean" -> "static/fonts/malgun.ttf";
|
||||
case "chinese" -> "static/fonts/SimSun.ttf";
|
||||
case "thai" -> "static/fonts/NotoSansThai-Regular.ttf";
|
||||
case "roman" -> "static/fonts/NotoSans-Regular.ttf";
|
||||
default -> "static/fonts/NotoSans-Regular.ttf";
|
||||
};
|
||||
|
||||
if (!"".equals(resourceDir)) {
|
||||
ClassPathResource classPathResource = new ClassPathResource(resourceDir);
|
||||
@ -327,30 +326,30 @@ public class StampController {
|
||||
throws IOException {
|
||||
float actualWidth =
|
||||
(text != null) ? calculateTextWidth(text, font, fontSize) : contentWidth;
|
||||
switch (position % 3) {
|
||||
return switch (position % 3) {
|
||||
case 1: // Left
|
||||
return pageSize.getLowerLeftX() + margin;
|
||||
yield pageSize.getLowerLeftX() + margin;
|
||||
case 2: // Center
|
||||
return (pageSize.getWidth() - actualWidth) / 2;
|
||||
yield (pageSize.getWidth() - actualWidth) / 2;
|
||||
case 0: // Right
|
||||
return pageSize.getUpperRightX() - actualWidth - margin;
|
||||
yield pageSize.getUpperRightX() - actualWidth - margin;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
yield 0;
|
||||
};
|
||||
}
|
||||
|
||||
private float calculatePositionY(
|
||||
PDRectangle pageSize, int position, float height, float margin) {
|
||||
switch ((position - 1) / 3) {
|
||||
return switch ((position - 1) / 3) {
|
||||
case 0: // Top
|
||||
return pageSize.getUpperRightY() - height - margin;
|
||||
yield pageSize.getUpperRightY() - height - margin;
|
||||
case 1: // Middle
|
||||
return (pageSize.getHeight() - height) / 2;
|
||||
yield (pageSize.getHeight() - height) / 2;
|
||||
case 2: // Bottom
|
||||
return pageSize.getLowerLeftY() + margin;
|
||||
yield pageSize.getLowerLeftY() + margin;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
yield 0;
|
||||
};
|
||||
}
|
||||
|
||||
private float calculateTextWidth(String text, PDFont font, float fontSize) throws IOException {
|
||||
|
@ -7,7 +7,6 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@ -106,7 +105,7 @@ public class PipelineProcessor {
|
||||
Map<String, Object> parameters = pipelineOperation.getParameters();
|
||||
List<String> inputFileTypes = apiDocService.getExtensionTypes(false, operation);
|
||||
if (inputFileTypes == null) {
|
||||
inputFileTypes = new ArrayList<String>(Arrays.asList("ALL"));
|
||||
inputFileTypes = new ArrayList<>(List.of("ALL"));
|
||||
}
|
||||
|
||||
if (!apiDocService.isValidOperation(operation, parameters)) {
|
||||
|
@ -186,6 +186,7 @@ public class CertSignController {
|
||||
"alias", privateKey, password.toCharArray(), new Certificate[] {cert});
|
||||
break;
|
||||
case "PKCS12":
|
||||
case "PFX":
|
||||
ks = KeyStore.getInstance("PKCS12");
|
||||
ks.load(p12File.getInputStream(), password.toCharArray());
|
||||
break;
|
||||
|
@ -9,6 +9,8 @@ import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
@ -317,6 +319,8 @@ public class GeneralWebController {
|
||||
return "remove-image-pdf";
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public class FontResource {
|
||||
|
||||
private String name;
|
||||
@ -331,28 +335,5 @@ public class GeneralWebController {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@ -362,6 +364,8 @@ public class MetricsController {
|
||||
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public static class EndpointCount {
|
||||
|
||||
private String endpoint;
|
||||
@ -373,20 +377,5 @@ public class MetricsController {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public String getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public void setEndpoint(String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
public double getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(double count) {
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,12 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Getter;
|
||||
|
||||
public class ApiEndpoint {
|
||||
private final String name;
|
||||
private Map<String, JsonNode> parameters;
|
||||
@Getter
|
||||
private final String description;
|
||||
|
||||
public ApiEndpoint(String name, JsonNode postNode) {
|
||||
@ -31,10 +33,6 @@ public class ApiEndpoint {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApiEndpoint [name=" + name + ", parameters=" + parameters + "]";
|
||||
|
@ -39,4 +39,9 @@ public class ConvertToImageRequest extends PDFWithPageNums {
|
||||
defaultValue = "300",
|
||||
requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Integer dpi;
|
||||
|
||||
@Schema(
|
||||
description = "Include annotations such as comments in the output image(s)",
|
||||
defaultValue = "false")
|
||||
private Boolean includeAnnotations;
|
||||
}
|
||||
|
@ -32,6 +32,13 @@ public class AddPageNumbersRequest extends PDFWithPageNums {
|
||||
requiredMode = RequiredMode.REQUIRED)
|
||||
private String fontType;
|
||||
|
||||
@Schema(
|
||||
description = "Hex colour for page numbers (e.g. #FF0000)",
|
||||
example = "#000000",
|
||||
defaultValue = "#000000",
|
||||
requiredMode = RequiredMode.NOT_REQUIRED)
|
||||
private String fontColor;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"Position: 1-9 representing positions on the page (1=top-left, 2=top-center,"
|
||||
|
@ -79,10 +79,6 @@ public class ScannerEffectRequest {
|
||||
@Schema(description = "Whether advanced settings are enabled", example = "false")
|
||||
private boolean advancedEnabled = false;
|
||||
|
||||
public boolean isAdvancedEnabled() {
|
||||
return advancedEnabled;
|
||||
}
|
||||
|
||||
public int getQualityValue() {
|
||||
return switch (quality) {
|
||||
case low -> 30;
|
||||
|
@ -15,20 +15,25 @@ public class SignPDFWithCertRequest extends PDFFile {
|
||||
|
||||
@Schema(
|
||||
description = "The type of the digital certificate",
|
||||
allowableValues = {"PEM", "PKCS12", "JKS"},
|
||||
allowableValues = {"PEM", "PKCS12", "PFX", "JKS"},
|
||||
requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String certType;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"The private key for the digital certificate (required for PEM type"
|
||||
+ " certificates)")
|
||||
+ " certificates, supports .pem, .der, or .key files)")
|
||||
private MultipartFile privateKeyFile;
|
||||
|
||||
@Schema(description = "The digital certificate (required for PEM type certificates)")
|
||||
@Schema(
|
||||
description =
|
||||
"The digital certificate (required for PEM type certificates, supports"
|
||||
+ " .pem, .der, .crt, or .cer files)")
|
||||
private MultipartFile certFile;
|
||||
|
||||
@Schema(description = "The PKCS12 keystore file (required for PKCS12 type certificates)")
|
||||
@Schema(
|
||||
description =
|
||||
"The PKCS12/PFX keystore file (required for PKCS12 or PFX type certificates)")
|
||||
private MultipartFile p12File;
|
||||
|
||||
@Schema(description = "The JKS keystore file (Java Key Store)")
|
||||
|
@ -6,6 +6,7 @@ import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.apache.pdfbox.text.TextPosition;
|
||||
@ -20,6 +21,7 @@ public class TextFinder extends PDFTextStripper {
|
||||
private final String searchTerm;
|
||||
private final boolean useRegex;
|
||||
private final boolean wholeWordSearch;
|
||||
@Getter
|
||||
private final List<PDFText> foundTexts = new ArrayList<>();
|
||||
|
||||
private final List<TextPosition> pageTextPositions = new ArrayList<>();
|
||||
@ -187,10 +189,6 @@ public class TextFinder extends PDFTextStripper {
|
||||
super.endPage(page);
|
||||
}
|
||||
|
||||
public List<PDFText> getFoundTexts() {
|
||||
return foundTexts;
|
||||
}
|
||||
|
||||
public String getDebugInfo() {
|
||||
StringBuilder debug = new StringBuilder();
|
||||
debug.append("Extracted text length: ").append(pageTextBuilder.length()).append("\n");
|
||||
|
@ -53,7 +53,7 @@ public class ApiDocService {
|
||||
|
||||
public List<String> getExtensionTypes(boolean output, String operationName) {
|
||||
if (outputToFileTypes.size() == 0) {
|
||||
outputToFileTypes.put("PDF", Arrays.asList("pdf"));
|
||||
outputToFileTypes.put("PDF", List.of("pdf"));
|
||||
outputToFileTypes.put(
|
||||
"IMAGE",
|
||||
Arrays.asList(
|
||||
@ -63,10 +63,10 @@ public class ApiDocService {
|
||||
"ZIP",
|
||||
Arrays.asList("zip", "rar", "7z", "tar", "gz", "bz2", "xz", "lz", "lzma", "z"));
|
||||
outputToFileTypes.put("WORD", Arrays.asList("doc", "docx", "odt", "rtf"));
|
||||
outputToFileTypes.put("CSV", Arrays.asList("csv"));
|
||||
outputToFileTypes.put("CSV", List.of("csv"));
|
||||
outputToFileTypes.put("JS", Arrays.asList("js", "jsx"));
|
||||
outputToFileTypes.put("HTML", Arrays.asList("html", "htm", "xhtml"));
|
||||
outputToFileTypes.put("JSON", Arrays.asList("json"));
|
||||
outputToFileTypes.put("JSON", List.of("json"));
|
||||
outputToFileTypes.put("TXT", Arrays.asList("txt", "text", "md", "markdown"));
|
||||
outputToFileTypes.put("PPT", Arrays.asList("ppt", "pptx", "odp"));
|
||||
outputToFileTypes.put("XML", Arrays.asList("xml", "xsd", "xsl"));
|
||||
|
@ -77,7 +77,7 @@ public class CertificateValidationService {
|
||||
try {
|
||||
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
List<X509Certificate> certList = Arrays.asList(cert);
|
||||
List<X509Certificate> certList = Collections.singletonList(cert);
|
||||
CertPath certPath = cf.generateCertPath(certList);
|
||||
|
||||
Set<TrustAnchor> anchors = new HashSet<>();
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=تحويل
|
||||
pdfToImage.info=Python غير مثبت. مطلوب لتحويل WebP.
|
||||
pdfToImage.placeholder=(مثال: 1,2,8 أو 4,7,12-16 أو 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Çevir
|
||||
pdfToImage.info=Python Yüklü Deyil.WebP Çevirməsi Üçün Vacibdir
|
||||
pdfToImage.placeholder=(məsələn, 1,2,8 və ya 4,7,12-16 və ya 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Преобразуване
|
||||
pdfToImage.info=Python не е инсталиран. Изисква се за конвертиране на WebP.
|
||||
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=བསྒྱུར་བ།
|
||||
pdfToImage.info=Python སྒྲིག་འཇུག་བྱས་མི་འདུག WebP བསྒྱུར་བར་དགོས་མཁོ་ཡིན།
|
||||
pdfToImage.placeholder=(དཔེར་ན། 1,2,8 ཡང་ན་ 4,7,12-16 ཡང་ན་ 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Converteix
|
||||
pdfToImage.info=Python no està instal·lat. És necessari per a la conversió a WebP.
|
||||
pdfToImage.placeholder=(p. ex. 1,2,8 o 4,7,12-16 o 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Převést
|
||||
pdfToImage.info=Python není nainstalován. Vyžadován pro konverzi do WebP.
|
||||
pdfToImage.placeholder=(např. 1,2,8 nebo 4,7,12-16 nebo 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Konvertér
|
||||
pdfToImage.info=Python er ikke installeret. Påkrævet for WebP-konvertering.
|
||||
pdfToImage.placeholder=(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=Die Datei muss im Format {0} vorliegen.
|
||||
error.invalidFormat=Ungültiges {0}-Format: {1}
|
||||
error.endpointDisabled=Dieser Endpunkt wurde vom Administrator deaktiviert.
|
||||
error.urlNotReachable=Die URL ist nicht erreichbar, bitte geben Sie eine gültige URL an.
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Umwandeln
|
||||
pdfToImage.info=Python ist nicht installiert. Erforderlich für die WebP-Konvertierung.
|
||||
pdfToImage.placeholder=(z.B. 1,2,8 oder 4,7,12-16 oder 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Μετατροπή
|
||||
pdfToImage.info=Η Python δεν είναι εγκατεστημένη. Απαιτείται για μετατροπή WebP.
|
||||
pdfToImage.placeholder=(π.χ. 1,2,8 ή 4,7,12-16 ή 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -137,6 +137,7 @@ lang.yor=Yoruba
|
||||
|
||||
addPageNumbers.fontSize=Font Size
|
||||
addPageNumbers.fontName=Font Name
|
||||
addPageNumbers.fontColor=Font Colour
|
||||
pdfPrompt=Select PDF(s)
|
||||
multiPdfPrompt=Select PDFs (2+)
|
||||
multiPdfDropPrompt=Select (or drag & drop) all PDFs you require
|
||||
@ -193,6 +194,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1441,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Convert
|
||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Convert
|
||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Convertir
|
||||
pdfToImage.info=Python no está instalado. Se requiere para la conversión WebP.
|
||||
pdfToImage.placeholder=(por ejemplo 1,2,8 o 4,7,12-16 o 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Bihurtu
|
||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||
pdfToImage.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=تبدیل
|
||||
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
|
||||
pdfToImage.placeholder=(مثال: 1,2,8 یا 4,7,12-16 یا 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=Le fichier doit être au format {0}
|
||||
error.invalidFormat=Format {0} invalide : {1}
|
||||
error.endpointDisabled=Ce point de terminaison a été désactivé par l'administrateur
|
||||
error.urlNotReachable=L'URL est inaccessible, veuillez fournir une URL valide
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Convertir
|
||||
pdfToImage.info=Python n'est pas installé. Nécessaire pour la conversion WebP.
|
||||
pdfToImage.placeholder=(par exemple : 1,2,8 ou 4,7,12-16 ou 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Tiontaigh
|
||||
pdfToImage.info=Níl Python suiteáilte. Ag teastáil le haghaidh comhshó WebP.
|
||||
pdfToImage.placeholder=(m.sh. 1,2,8 nó 4,7,12-16 nó 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=बदलें
|
||||
pdfToImage.info=Python स्थापित नहीं है। WebP रूपांतरण के लिए आवश्यक है।
|
||||
pdfToImage.placeholder=(जैसे 1,2,8 या 4,7,12-16 या 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Pretvori
|
||||
pdfToImage.info=Python nije instaliran. Treba je za konverziju na WebP.
|
||||
pdfToImage.placeholder=(t.j. 1,2,8 ili 4,7,12-16 ili 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=A fájlnak {0} formátumúnak kell lennie
|
||||
error.invalidFormat=Érvénytelen {0} formátum: {1}
|
||||
error.endpointDisabled=Ezt a végpontot a rendszergazda letiltotta
|
||||
error.urlNotReachable=Az URL nem érhető el, kérjük, adjon meg érvényes URL-t
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Konvertálás
|
||||
pdfToImage.info=Python nincs telepítve. WebP konverzióhoz szükséges.
|
||||
pdfToImage.placeholder=(pl. 1,2,8 vagy 4,7,12-16 vagy 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Konversi
|
||||
pdfToImage.info=Python tidak terinstal. Diperlukan untuk konversi WebP.
|
||||
pdfToImage.placeholder=(misalnya 1,2,8 atau 4,7,12-16 atau 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=Il file deve essere nel formato {0}
|
||||
error.invalidFormat=Formato {0} non valido:{1}
|
||||
error.endpointDisabled=Questo endpoint è stato disabilitato dall'amministratore
|
||||
error.urlNotReachable=L'URL non è raggiungibile, inserisci un URL valido
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -908,7 +909,7 @@ login.alreadyLoggedIn=Hai già effettuato l'accesso a
|
||||
login.alreadyLoggedIn2=dispositivi. Esci dai dispositivi e riprova.
|
||||
login.toManySessions=Hai troppe sessioni attive
|
||||
login.logoutMessage=Sei stato disconnesso.
|
||||
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
|
||||
login.invalidInResponseTo=La risposta SAML richiesta non è valida o è scaduta. Contattare l'amministratore.
|
||||
|
||||
#auto-redact
|
||||
autoRedact.title=Redazione automatica
|
||||
@ -1435,10 +1436,11 @@ pdfToImage.colorType=Tipo di colore
|
||||
pdfToImage.color=A colori
|
||||
pdfToImage.grey=Scala di grigi
|
||||
pdfToImage.blackwhite=Bianco e Nero (potresti perdere dettagli!)
|
||||
pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.dpi=DPI (Il limite del server è {0} dpi)
|
||||
pdfToImage.submit=Converti
|
||||
pdfToImage.info=Python non è installato.È richiesto per la conversione WebP.
|
||||
pdfToImage.placeholder=(es. 1,2,8 o 4,7,12-16 o 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=ファイルは{0}形式である必要があります
|
||||
error.invalidFormat=無効な{0}形式: {1}
|
||||
error.endpointDisabled=このエンドポイントは管理者によって無効になっています
|
||||
error.urlNotReachable=URLにアクセスできません。有効なURLを入力してください
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=変換
|
||||
pdfToImage.info=Pythonがインストールされていません。WebPの変換に必要です。
|
||||
pdfToImage.placeholder=(例:1,2,8、4,7,12-16、2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=변환
|
||||
pdfToImage.info=WebP 변환에는 Python이 필요합니다. Python이 설치되지 않았습니다.
|
||||
pdfToImage.placeholder=(예: 1,2,8 또는 4,7,12-16 또는 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=പരിവർത്തനം ചെയ്യുക
|
||||
pdfToImage.info=പൈത്തൺ ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ല. WebP പരിവർത്തനത്തിന് ആവശ്യമാണ്.
|
||||
pdfToImage.placeholder=(ഉദാ. 1,2,8 അല്ലെങ്കിൽ 4,7,12-16 അല്ലെങ്കിൽ 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Omzetten
|
||||
pdfToImage.info=Python is niet geïnstalleerd. Vereist voor WebP-conversie.
|
||||
pdfToImage.placeholder=(bijv. 1,2,8 of 4,7,12-16 of 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Konverter
|
||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||
pdfToImage.placeholder=(f.eks. 1,2,8 eller 4,7,12-16 eller 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Konwertuj
|
||||
pdfToImage.info=Python nie został zainstalowany. Jest wymagany do konwersji WebP.
|
||||
pdfToImage.placeholder=(przykład 1,2,8 lub 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Converter
|
||||
pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
|
||||
pdfToImage.placeholder=(por exemplo 1,2,8 ou 4,7,12-16 ou 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Converter
|
||||
pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
|
||||
pdfToImage.placeholder=(ex. 1,2,8 ou 4,7,12-16 ou 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Convertește
|
||||
pdfToImage.info=Python nu este instalat. Necesar pentru conversia WebP.
|
||||
pdfToImage.placeholder=(ex. 1,2,8 sau 4,7,12-16 sau 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=Файл должен быть в формате {0}
|
||||
error.invalidFormat=Недопустимый формат {0}: {1}
|
||||
error.endpointDisabled=Эта конечная точка была отключена администратором
|
||||
error.urlNotReachable=URL-адрес недоступен, пожалуйста, укажите действительный URL-адрес
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Преобразовать
|
||||
pdfToImage.info=Python не установлен. Требуется для конвертации в WebP.
|
||||
pdfToImage.placeholder=(например, 1,2,8 или 4,7,12-16 или 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Konvertovať
|
||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||
pdfToImage.placeholder=(napr. 1,2,8 alebo 4,7,12-16 alebo 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Pretvori
|
||||
pdfToImage.info=Python ni nameščen. Zahtevano za pretvorbo WebP.
|
||||
pdfToImage.placeholder=(npr. 1,2,8 ali 4,7,12-16 ali 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Konvertuj
|
||||
pdfToImage.info=Python nije instaliran. Neophodan je za WebP konverziju.
|
||||
pdfToImage.placeholder=(npr. 1,2,8 ili 4,7,12-16 ili 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Konvertera
|
||||
pdfToImage.info=Python är inte installerat. Krävs för WebP-konvertering.
|
||||
pdfToImage.placeholder=(t.ex. 1,2,8 eller 4,7,12-16 eller 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=แปลง
|
||||
pdfToImage.info=Python ไม่มีการติดตั้ง จำเป็นสำหรับการแปลง WebP
|
||||
pdfToImage.placeholder=(เช่น 1,2,8 หรือ 4,7,12-16 หรือ 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -1,7 +1,7 @@
|
||||
###########
|
||||
# Generic #
|
||||
###########
|
||||
# the direction that the language is written (ltr=left to right, rtl=right to left)
|
||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||
language.direction=ltr
|
||||
|
||||
# Language names for reuse throughout the application
|
||||
@ -193,6 +193,7 @@ error.fileFormatRequired=Dosya {0} formatında olmalıdır
|
||||
error.invalidFormat=Geçersiz {0} formatı: {1}
|
||||
error.endpointDisabled=Bu uç nokta yönetici tarafından devre dışı bırakılmıştır
|
||||
error.urlNotReachable=URL erişilebilir değil, lütfen geçerli bir URL sağlayın
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (Sunucu limiti {0} dpi)
|
||||
pdfToImage.submit=Dönüştür
|
||||
pdfToImage.info=Python kurulu değil. WebP dönüşümü için gereklidir.
|
||||
pdfToImage.placeholder=(örneğin 1,2,8 veya 4,7,12-16 ya da 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Конвертувати
|
||||
pdfToImage.info=Python не встановлено. Необхідно для конвертації WebP.
|
||||
pdfToImage.placeholder=(наприклад 1,2,8 або 4,7,12-16 або 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=This endpoint has been disabled by the admin
|
||||
error.urlNotReachable=URL is not reachable, please provide a valid URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=Chuyển đổi
|
||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||
pdfToImage.placeholder=(ví dụ: 1,2,8 hoặc 4,7,12-16 hoặc 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=File must be in {0} format
|
||||
error.invalidFormat=Invalid {0} format: {1}
|
||||
error.endpointDisabled=该端点被管理员禁用
|
||||
error.urlNotReachable=URL无法访问,请提供有效的URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=转换
|
||||
pdfToImage.info=WebP 转换需要安装 Python
|
||||
pdfToImage.placeholder=(例如:1,2,8 或 4,7,12-16 或 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -193,6 +193,7 @@ error.fileFormatRequired=檔案必須為 {0} 格式
|
||||
error.invalidFormat=無效的 {0} 格式:{1}
|
||||
error.endpointDisabled=此端點已被管理員停用
|
||||
error.urlNotReachable=無法連線至 URL,請提供有效的 URL
|
||||
error.invalidUrlFormat=Invalid URL format provided. The provided format is invalid.
|
||||
|
||||
# DPI and image rendering messages - used by frontend for dynamic translation
|
||||
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
|
||||
@ -1439,6 +1440,7 @@ pdfToImage.dpi=DPI (The server limit is {0} dpi)
|
||||
pdfToImage.submit=轉換
|
||||
pdfToImage.info=尚未安裝 Python。需要安裝 Python 才能進行 WebP 轉換。
|
||||
pdfToImage.placeholder=(例如 1,2,8 或 4,7,12-16 或 2n-1)
|
||||
pdfToImage.includeAnnotations=Include annotations (comments, highlights etc.)
|
||||
|
||||
|
||||
#addPassword
|
||||
|
@ -151,6 +151,8 @@ system:
|
||||
cleanupIntervalMinutes: 30 # How often to run cleanup (in minutes)
|
||||
startupCleanup: true # Clean up old temp files on startup
|
||||
cleanupSystemTemp: false # Whether to clean broader system temp directory
|
||||
databaseBackup:
|
||||
cron: '0 0 0 * * ?' # Cron expression for automatic database backups "0 0 0 * * ?" daily at midnight
|
||||
|
||||
ui:
|
||||
appName: '' # application's visible name
|
||||
|
@ -24,7 +24,7 @@
|
||||
{
|
||||
"moduleName": "com.bucket4j:bucket4j_jdk17-core",
|
||||
"moduleUrl": "http://github.com/bucket4j/bucket4j/bucket4j_jdk17-core",
|
||||
"moduleVersion": "8.14.0",
|
||||
"moduleVersion": "8.15.0",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
@ -536,6 +536,13 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "commons-io:commons-io",
|
||||
"moduleUrl": "https://commons.apache.org/proper/commons-io/",
|
||||
"moduleVersion": "2.19.0",
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "commons-io:commons-io",
|
||||
"moduleUrl": "https://commons.apache.org/proper/commons-io/",
|
||||
@ -559,21 +566,21 @@
|
||||
{
|
||||
"moduleName": "io.jsonwebtoken:jjwt-api",
|
||||
"moduleUrl": "https://github.com/jwtk/jjwt",
|
||||
"moduleVersion": "0.12.7",
|
||||
"moduleVersion": "0.13.0",
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.jsonwebtoken:jjwt-impl",
|
||||
"moduleUrl": "https://github.com/jwtk/jjwt",
|
||||
"moduleVersion": "0.12.7",
|
||||
"moduleVersion": "0.13.0",
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "io.jsonwebtoken:jjwt-jackson",
|
||||
"moduleUrl": "https://github.com/jwtk/jjwt",
|
||||
"moduleVersion": "0.12.7",
|
||||
"moduleVersion": "0.13.0",
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
@ -738,6 +745,13 @@
|
||||
"moduleLicense": "GPL2 w/ CPE",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "jakarta.servlet:jakarta.servlet-api",
|
||||
"moduleUrl": "https://www.eclipse.org",
|
||||
"moduleVersion": "6.1.0",
|
||||
"moduleLicense": "GPL2 w/ CPE",
|
||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "jakarta.transaction:jakarta.transaction-api",
|
||||
"moduleUrl": "https://projects.eclipse.org/projects/ee4j.jta",
|
||||
@ -862,6 +876,20 @@
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.apache.commons:commons-lang3",
|
||||
"moduleUrl": "https://commons.apache.org/proper/commons-lang/",
|
||||
"moduleVersion": "3.18.0",
|
||||
"moduleLicense": "Apache-2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.apache.commons:commons-text",
|
||||
"moduleUrl": "https://commons.apache.org/proper/commons-text",
|
||||
"moduleVersion": "1.10.0",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.apache.commons:commons-text",
|
||||
"moduleUrl": "https://commons.apache.org/proper/commons-text",
|
||||
@ -984,6 +1012,13 @@
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.bouncycastle:bcpkix-jdk18on",
|
||||
"moduleUrl": "https://www.bouncycastle.org/java.html",
|
||||
"moduleVersion": "1.72",
|
||||
"moduleLicense": "Bouncy Castle Licence",
|
||||
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.bouncycastle:bcpkix-jdk18on",
|
||||
"moduleUrl": "https://www.bouncycastle.org/download/bouncy-castle-java/",
|
||||
@ -998,6 +1033,13 @@
|
||||
"moduleLicense": "Bouncy Castle Licence",
|
||||
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.bouncycastle:bcutil-jdk18on",
|
||||
"moduleUrl": "https://www.bouncycastle.org/java.html",
|
||||
"moduleVersion": "1.72",
|
||||
"moduleLicense": "Bouncy Castle Licence",
|
||||
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.bouncycastle:bcutil-jdk18on",
|
||||
"moduleUrl": "https://www.bouncycastle.org/download/bouncy-castle-java/",
|
||||
@ -1471,19 +1513,19 @@
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springdoc:springdoc-openapi-starter-common",
|
||||
"moduleVersion": "2.8.11",
|
||||
"moduleVersion": "2.8.12",
|
||||
"moduleLicense": "The Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springdoc:springdoc-openapi-starter-webmvc-api",
|
||||
"moduleVersion": "2.8.11",
|
||||
"moduleVersion": "2.8.12",
|
||||
"moduleLicense": "The Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springdoc:springdoc-openapi-starter-webmvc-ui",
|
||||
"moduleVersion": "2.8.11",
|
||||
"moduleVersion": "2.8.12",
|
||||
"moduleLicense": "The Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
@ -1515,6 +1557,13 @@
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-devtools",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
"moduleVersion": "3.5.5",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-starter",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
@ -1822,7 +1871,7 @@
|
||||
{
|
||||
"moduleName": "org.webjars:swagger-ui",
|
||||
"moduleUrl": "https://www.webjars.org",
|
||||
"moduleVersion": "5.27.1",
|
||||
"moduleVersion": "5.28.0",
|
||||
"moduleLicense": "Apache-2.0"
|
||||
},
|
||||
{
|
||||
|
@ -73,6 +73,10 @@
|
||||
<option value="blackwhite" th:text="#{pdfToImage.blackwhite}"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" id="includeAnnotations" name="includeAnnotations">
|
||||
<label class="form-check-label" for="includeAnnotations" th:text="#{pdfToImage.includeAnnotations}"></label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="dpi">DPI:</label>
|
||||
<input type="number" name="dpi" class="form-control" id="dpi" min="1" step="1" value="300" required>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user