Compare commits

...

26 Commits

Author SHA1 Message Date
Ludy
cb7471024b
feat(common): add ChecksumUtils for MD5/SHA*/CRC32/Adler32 with Base64 and multi-algorithm support (#4261) 2025-09-04 15:38:28 +01:00
Balázs Szücs
74870615df
Replace uses of Arrays.asList() with either List.of() or Collections.singletonList() (#4219) 2025-09-04 15:30:45 +01:00
Ludy
02d096d622
feat(security): add PFX alias for PKCS12; accept .crt/.cer/.der certs & .key keys; add certificate-signing tests (#4297) 2025-09-04 15:30:32 +01:00
Ludy
0d7649bee8
fix(ci:testdriver): conditionally run frontend tests based on file changes (#4064)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-04 15:11:09 +01:00
Ludy
0776ecc96b
test(common): add dedicated unit tests for FileInfo and InputStreamTemplateResource (#4140) 2025-09-04 15:10:35 +01:00
Ludy
8113728d3d
feat(database): make backup schedule configurable via system keys (#4251) 2025-09-04 15:02:31 +01:00
albanobattistella
528968bfe9
Update messages_it_IT.properties (#4285)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-09-04 14:50:16 +01:00
dependabot[bot]
61b85a9273
build(deps): bump jakarta.mail:jakarta.mail-api from 2.1.3 to 2.1.4 (#4351)
Bumps
[jakarta.mail:jakarta.mail-api](https://github.com/jakartaee/mail-api)
from 2.1.3 to 2.1.4.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="0d13f04450"><code>0d13f04</code></a>
Prepare release jakarta.mail:jakarta.mail-api:2.1.4</li>
<li><a
href="958fb97ab1"><code>958fb97</code></a>
services/jakarta.mail.Provider override not working <a
href="https://redirect.github.com/jakartaee/mail-api/issues/777">#777</a>
(<a
href="https://redirect.github.com/jakartaee/mail-api/issues/779">#779</a>)</li>
<li><a
href="3446c94241"><code>3446c94</code></a>
services/jakarta.mail.Provider override not working <a
href="https://redirect.github.com/jakartaee/mail-api/issues/170">#170</a>
(<a
href="https://redirect.github.com/jakartaee/mail-api/issues/778">#778</a>)</li>
<li><a
href="892fae4ac7"><code>892fae4</code></a>
Multipart performs blocking call in every instantiation <a
href="https://redirect.github.com/jakartaee/mail-api/issues/699">#699</a>
(<a
href="https://redirect.github.com/jakartaee/mail-api/issues/716">#716</a>)</li>
<li><a
href="666ec999d8"><code>666ec99</code></a>
Bump rexml from 3.2.8 to 3.3.6 in /www</li>
<li><a
href="8eddc342b1"><code>8eddc34</code></a>
Bump rexml from 3.2.5 to 3.2.8 in /www</li>
<li><a
href="1259b86a8c"><code>1259b86</code></a>
Bump nokogiri from 1.16.2 to 1.16.5 in /www</li>
<li><a
href="bf2bfc18c0"><code>bf2bfc1</code></a>
Update README.md</li>
<li><a
href="038fa7038a"><code>038fa70</code></a>
Prepare next development cycle for 2.1.4-SNAPSHOT</li>
<li><a
href="1e52027546"><code>1e52027</code></a>
Prepare release jakarta.mail:jakarta.mail-api:2.1.3</li>
<li>See full diff in <a
href="https://github.com/jakartaee/mail-api/compare/2.1.3...2.1.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=jakarta.mail:jakarta.mail-api&package-manager=gradle&previous-version=2.1.3&new-version=2.1.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 14:50:03 +01:00
Ludy
c055f9456a
feat(convert): PDF conversion with unoconvert fallback soffice (#4316)
# Description of Changes

- **What was changed**
- Reworked `ConvertOfficeController` to use a dedicated temporary
working directory per request and cleaned up with directory-level
deletion.
- Added detection for converter availability via `EndpointConfiguration`
to choose between **unoconvert** and a **soffice** headless fallback.
- Ensured safe filename handling (sanitization, extension checks,
lowercase normalization) and early validation errors for missing/invalid
filenames.
- Switched raw temp file writes to `Files.copy` / `Files.writeString`
with `StandardCopyOption.REPLACE_EXISTING`.
  - Implemented robust output handling:
    - Verified non-zero exit codes and null results.
    - Checked for missing/empty PDF outputs.
- Added fallback lookup for any produced `.pdf` within the work
directory if the expected name is not present.
  - Introduced `@Slf4j` logging; improved error and cleanup logging.
- Replaced ad-hoc temp cleanup with `FileUtils.deleteDirectory` for full
working-dir removal.
- Minor imports/cleanup: removed unused `Arrays`, added
`StandardCopyOption`, `FileUtils`, and related imports.

- **Why the change was made**
- Increase conversion reliability across environments where either
unoconvert or soffice may be available.
- Harden security and stability through strict input validation and
sanitized HTML processing.
- Prevent orphaned files/directories and ensure consistent cleanup to
reduce disk footprint and operational issues.
- Provide clearer operational signals (logging, explicit exceptions) for
easier troubleshooting.


---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-09-04 14:33:35 +01:00
Balázs Szücs
fe84b3ff15
feat: Add Lombok @Getter and @Setter annotations to reduce boilerplate code in multiple classes (#4321)
# Description of Changes

Update classes across the codebase to use Lombok's `@Getter` and
`@Setter` annotations, replacing manually written getter and setter
methods. This change streamlines the code, reduces boilerplate, and
improves maintainability.

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-04 14:29:55 +01:00
Ludy
9a213c4bf6
feat(misc): Add font color option for page numbers; improve alignment & robustness (#4334)
# Description of Changes

**What was changed**
- **API & backend**
- Added optional `fontColor` (hex, e.g. `#FF0000`) to
`AddPageNumbersRequest` with OpenAPI docs, default `#000000`.
- Decode hex color with safe fallback to black; apply via
`setNonStrokingColor`.
- Switched multiple `switch` statements to concise switch expressions
and used `Locale.ROOT` for case operations.
- Clamped `position` to `1..9` and reworked alignment using proper font
metrics (`ascent`/`descent`) for top/middle/bottom positioning.
  - Centralized filename base extraction; reduced repeated calls.  
  - Used try-with-resources for `PDPageContentStream`.
- **UI & i18n**
  - Added `addPageNumbers.fontColor` label (en_GB).  
- Introduced `<input type="color" id="fontColor" ...>` with live
background preview in the Add Page Numbers form.

**Why the change was made**
- Enable users to render page numbers in a chosen color (not just
black).
- Produce visually correct placement by accounting for font metrics
(baseline vs. optical middle).
- Improve resilience (locale-safe parsing, bounds checking) and code
clarity.

Closes #3839


[after.pdf](https://github.com/user-attachments/files/22064425/after.pdf)

[before.pdf](https://github.com/user-attachments/files/22064426/before.pdf)


---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-09-04 14:04:11 +01:00
Ludy
a4a57cef92
fix(api): prevent MultipartFile binding errors in StampController (#4331)
# Description of Changes

- **What was changed**
- Added a Spring `@InitBinder` in `StampController` that registers a
`PropertyEditorSupport` for `MultipartFile` to safely handle text inputs
by setting the value to `null`.
- Replaced multiple `switch` statements with modern Java **switch
expressions**:
    - Margin selection (`customMargin`) now uses a concise expression.
- Font selection (`alphabet` → font resource path) rewritten as an
expression.
- Position calculations (`calculatePositionX` / `calculatePositionY`)
now return via switch expressions with `yield`.
- Minor readability and maintainability improvements without changing
public API or behavior (besides the binding fix).

- **Why the change was made**
- The `@InitBinder` prevents conversion/binding issues when Spring
attempts to map string form fields to `MultipartFile`, which could cause
exceptions or unexpected behavior in multipart/form-data requests.
- Using switch expressions reduces boilerplate, clarifies intent, and
makes the control flow safer and more maintainable.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-09-04 13:55:09 +01:00
stirlingbot[bot]
6f6f4a14dc
🌐 Sync Translations + Update README Progress Table (#4374)
### Description of Changes

This Pull Request was automatically generated to synchronize updates to
translation files and documentation. Below are the details of the
changes made:

#### **1. Synchronization of Translation Files**
- Updated translation files (`messages_*.properties`) to reflect changes
in the reference file `messages_en_GB.properties`.
- Ensured consistency and synchronization across all supported language
files.
- Highlighted any missing or incomplete translations.

#### **2. Update README.md**
- Generated the translation progress table in `README.md`.
- Added a summary of the current translation status for all supported
languages.
- Included up-to-date statistics on translation coverage.

#### **Why these changes are necessary**
- Keeps translation files aligned with the latest reference updates.
- Ensures the documentation reflects the current translation progress.

---

Auto-generated by [create-pull-request][1].

[1]: https://github.com/peter-evans/create-pull-request

---------

Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2025-09-04 12:56:18 +01:00
stirlingbot[bot]
c50aadeb35
🤖 format everything with pre-commit by stirlingbot (#4185)
Auto-generated by [create-pull-request][1] with **stirlingbot**

[1]: https://github.com/peter-evans/create-pull-request

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2025-09-04 12:56:02 +01:00
Ludy
963b4ee69d
refactor(ssrf): default enum MEDIUM prevents OFF=false (#4280)
# Description of Changes

- **What was changed**
  - **URL to PDF flow**
- Changed `ConvertWebsiteToPDF#urlToPdf` to return `ResponseEntity<?>`
and perform a redirect (`303 SEE_OTHER`) back to `/url-to-pdf` with an
`error` query param instead of throwing exceptions.
- Added alert rendering in `url-to-pdf.html` using `param.error` for
localized error display.
- Introduced new translation key `error.invalidUrlFormat` in
`messages_en_GB.properties`.
  - **Security / SSRF**
- Migrated `ApplicationProperties.System.UrlSecurity.level` from
`String` to `SsrfProtectionLevel` enum.
- Default now set to `SsrfProtectionLevel.MEDIUM` (`// MAX, MEDIUM,
OFF`).
- This avoids the issue where setting `OFF` returned `false` in
configuration parsing.
- Updated `SsrfProtectionService#parseProtectionLevel` accordingly
(using `level.name()`).
  - **Repo hygiene**
    - Added `**/LOCAL_APPDATA_FONTCONFIG_CACHE/**` to `.gitignore`.

- **Why the change was made**
- Provide user-friendly, localized error messages instead of exposing
internal exceptions on URL-to-PDF conversions.
- Ensure SSRF protection level parsing is type-safe and consistent—`OFF`
can now be set without yielding a misleading `false` state.
  - Prevent unwanted fontconfig cache files from being tracked in Git.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
2025-09-04 12:39:37 +01:00
stirlingbot[bot]
cd76f5e50a
Update 3rd Party Licenses (#4278)
Auto-generated by stirlingbot[bot]

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2025-09-04 12:37:39 +01:00
stirlingbot[bot]
763d50ba8d
🌐 Sync Translations + Update README Progress Table (#4277)
### Description of Changes

This Pull Request was automatically generated to synchronize updates to
translation files and documentation. Below are the details of the
changes made:

#### **1. Synchronization of Translation Files**
- Updated translation files (`messages_*.properties`) to reflect changes
in the reference file `messages_en_GB.properties`.
- Ensured consistency and synchronization across all supported language
files.
- Highlighted any missing or incomplete translations.

#### **2. Update README.md**
- Generated the translation progress table in `README.md`.
- Added a summary of the current translation status for all supported
languages.
- Included up-to-date statistics on translation coverage.

#### **Why these changes are necessary**
- Keeps translation files aligned with the latest reference updates.
- Ensures the documentation reflects the current translation progress.

---

Auto-generated by [create-pull-request][1].

[1]: https://github.com/peter-evans/create-pull-request

---------

Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2025-09-04 12:37:07 +01:00
dependabot[bot]
4987932f60
build(deps): bump org.panteleyev.jpackageplugin from 1.7.3 to 1.7.5 (#4347)
Bumps org.panteleyev.jpackageplugin from 1.7.3 to 1.7.5.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.panteleyev.jpackageplugin&package-manager=gradle&previous-version=1.7.3&new-version=1.7.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 12:31:56 +01:00
dependabot[bot]
1036befaf1
build(deps): bump com.bucket4j:bucket4j_jdk17-core from 8.14.0 to 8.15.0 (#4279)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps
[com.bucket4j:bucket4j_jdk17-core](https://github.com/bucket4j/bucket4j)
from 8.14.0 to 8.15.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/bucket4j/bucket4j/releases">com.bucket4j:bucket4j_jdk17-core's
releases</a>.</em></p>
<blockquote>
<h2>8.15.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix typo in previous-releases link by <a
href="https://github.com/DominiQN"><code>@​DominiQN</code></a> in <a
href="https://redirect.github.com/bucket4j/bucket4j/pull/533">bucket4j/bucket4j#533</a></li>
<li>Fix typo in verbose-api docs by <a
href="https://github.com/cmg1411"><code>@​cmg1411</code></a> in <a
href="https://redirect.github.com/bucket4j/bucket4j/pull/540">bucket4j/bucket4j#540</a></li>
<li>Fix comment in redisson.adoc by <a
href="https://github.com/K-jun98"><code>@​K-jun98</code></a> in <a
href="https://redirect.github.com/bucket4j/bucket4j/pull/541">bucket4j/bucket4j#541</a></li>
<li>Add valid example using Redisson library by <a
href="https://github.com/JoshWein"><code>@​JoshWein</code></a> in <a
href="https://redirect.github.com/bucket4j/bucket4j/pull/542">bucket4j/bucket4j#542</a></li>
<li>MongoDB backend by <a
href="https://github.com/granikartem"><code>@​granikartem</code></a> in
<a
href="https://redirect.github.com/bucket4j/bucket4j/pull/549">bucket4j/bucket4j#549</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/DominiQN"><code>@​DominiQN</code></a>
made their first contribution in <a
href="https://redirect.github.com/bucket4j/bucket4j/pull/533">bucket4j/bucket4j#533</a></li>
<li><a href="https://github.com/cmg1411"><code>@​cmg1411</code></a> made
their first contribution in <a
href="https://redirect.github.com/bucket4j/bucket4j/pull/540">bucket4j/bucket4j#540</a></li>
<li><a href="https://github.com/K-jun98"><code>@​K-jun98</code></a> made
their first contribution in <a
href="https://redirect.github.com/bucket4j/bucket4j/pull/541">bucket4j/bucket4j#541</a></li>
<li><a href="https://github.com/JoshWein"><code>@​JoshWein</code></a>
made their first contribution in <a
href="https://redirect.github.com/bucket4j/bucket4j/pull/542">bucket4j/bucket4j#542</a></li>
<li><a
href="https://github.com/granikartem"><code>@​granikartem</code></a>
made their first contribution in <a
href="https://redirect.github.com/bucket4j/bucket4j/pull/549">bucket4j/bucket4j#549</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/bucket4j/bucket4j/compare/8.14.0...8.15.0">https://github.com/bucket4j/bucket4j/compare/8.14.0...8.15.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="1ecc3152b8"><code>1ecc315</code></a>
Documentation for release 8.15.0</li>
<li><a
href="0d257fd2f4"><code>0d257fd</code></a>
Documentation for release 8.15.0</li>
<li><a
href="621f5d5804"><code>621f5d5</code></a>
Documentation for release 8.15.0</li>
<li><a
href="2930d8388b"><code>2930d83</code></a>
<a
href="https://redirect.github.com/bucket4j/bucket4j/issues/549">#549</a>
documentations</li>
<li><a
href="9b7f66a80f"><code>9b7f66a</code></a>
Changes according to the OSSRH Sunset <a
href="https://central.sonatype.org/pages/ossr">https://central.sonatype.org/pages/ossr</a>...</li>
<li><a
href="a9dae860ea"><code>a9dae86</code></a>
<a
href="https://redirect.github.com/bucket4j/bucket4j/issues/549">#549</a>
do not insist on specific the mongo-driver versions</li>
<li><a
href="4c4f1b9f7a"><code>4c4f1b9</code></a>
<a
href="https://redirect.github.com/bucket4j/bucket4j/issues/549">#549</a>
add project names</li>
<li><a
href="3e024b0fb0"><code>3e024b0</code></a>
<a
href="https://redirect.github.com/bucket4j/bucket4j/issues/549">#549</a>
add license</li>
<li><a
href="70e9cf58f9"><code>70e9cf5</code></a>
<a
href="https://redirect.github.com/bucket4j/bucket4j/issues/549">#549</a>
fix modular-name</li>
<li><a
href="1cbc7e1f3e"><code>1cbc7e1</code></a>
<a
href="https://redirect.github.com/bucket4j/bucket4j/issues/549">#549</a>
fix maven configuration</li>
<li>Additional commits viewable in <a
href="https://github.com/bucket4j/bucket4j/compare/8.14.0...8.15.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.bucket4j:bucket4j_jdk17-core&package-manager=gradle&previous-version=8.14.0&new-version=8.15.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 12:29:40 +01:00
dependabot[bot]
8a4acd4c98
build(deps): bump org.sonarqube from 6.2.0.5505 to 6.3.1.5724 (#4352)
Bumps org.sonarqube from 6.2.0.5505 to 6.3.1.5724.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.sonarqube&package-manager=gradle&previous-version=6.2.0.5505&new-version=6.3.1.5724)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 12:29:30 +01:00
dependabot[bot]
f93d8511e8
build(deps): bump actions/dependency-review-action from 4.7.2 to 4.7.3 (#4353)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps
[actions/dependency-review-action](https://github.com/actions/dependency-review-action)
from 4.7.2 to 4.7.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/dependency-review-action/releases">actions/dependency-review-action's
releases</a>.</em></p>
<blockquote>
<h2>4.7.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Add explicit permissions to workflow files by <a
href="https://github.com/AshelyTC"><code>@​AshelyTC</code></a> in <a
href="https://redirect.github.com/actions/dependency-review-action/pull/966">actions/dependency-review-action#966</a></li>
<li>Claire153/fix spamming mentioned issue by <a
href="https://github.com/claire153"><code>@​claire153</code></a> in <a
href="https://redirect.github.com/actions/dependency-review-action/pull/974">actions/dependency-review-action#974</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/dependency-review-action/compare/v4...v4.7.3">https://github.com/actions/dependency-review-action/compare/v4...v4.7.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="595b5aeba7"><code>595b5ae</code></a>
Update package version (<a
href="https://redirect.github.com/actions/dependency-review-action/issues/975">#975</a>)</li>
<li><a
href="fc5fd661aa"><code>fc5fd66</code></a>
Claire153/fix spamming mentioned issue (<a
href="https://redirect.github.com/actions/dependency-review-action/issues/974">#974</a>)</li>
<li><a
href="d38d1a4f40"><code>d38d1a4</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/dependency-review-action/issues/965">#965</a>
from actions/dependabot/npm_and_yarn/multi-c22e25d29b</li>
<li><a
href="8d420b827c"><code>8d420b8</code></a>
Merge branch 'main' into dependabot/npm_and_yarn/multi-c22e25d29b</li>
<li><a
href="bde01290d3"><code>bde0129</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/dependency-review-action/issues/966">#966</a>
from actions/ashelytc/add-permissions</li>
<li><a
href="ab524903e8"><code>ab52490</code></a>
remove ruby</li>
<li><a
href="ef00a0afbb"><code>ef00a0a</code></a>
add permissions to workflows</li>
<li><a
href="74c8179d39"><code>74c8179</code></a>
Bump brace-expansion</li>
<li>See full diff in <a
href="bc41886e18...595b5aeba7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/dependency-review-action&package-manager=github_actions&previous-version=4.7.2&new-version=4.7.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 12:28:46 +01:00
dependabot[bot]
54c7b0e689
build(deps): bump actions/setup-java from 4.7.1 to 5.0.0 (#4269)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from
4.7.1 to 5.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-java/releases">actions/setup-java's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<h3>Breaking Changes</h3>
<ul>
<li>Upgrade to node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/888">actions/setup-java#888</a></li>
</ul>
<p>Make sure your runner is updated to this version or newer to use this
release. v2.327.1 <a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<h3>Dependency Upgrades</h3>
<ul>
<li>Upgrade Publish Immutable Action by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-java/pull/798">actions/setup-java#798</a></li>
<li>Upgrade eslint-plugin-jest from 27.9.0 to 28.11.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/730">actions/setup-java#730</a></li>
<li>Upgrade undici from 5.28.5 to 5.29.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/833">actions/setup-java#833</a></li>
<li>Upgrade form-data to bring in fix for critical vulnerability by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/887">actions/setup-java#887</a></li>
<li>Upgrade actions/checkout from 4 to 5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/896">actions/setup-java#896</a></li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>Prevent default installation of JetBrains pre-releases by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-java/pull/859">actions/setup-java#859</a></li>
<li>Improve Error Handling for Setup-Java Action to Help Debug
Intermittent Failures by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/848">actions/setup-java#848</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-java/pull/848">actions/setup-java#848</a></li>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-java/pull/888">actions/setup-java#888</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-java/compare/v4...v5.0.0">https://github.com/actions/setup-java/compare/v4...v5.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="dded088883"><code>dded088</code></a>
Bump actions/checkout from 4 to 5 (<a
href="https://redirect.github.com/actions/setup-java/issues/896">#896</a>)</li>
<li><a
href="0913e9a06e"><code>0913e9a</code></a>
Upgrade to node 24 (<a
href="https://redirect.github.com/actions/setup-java/issues/888">#888</a>)</li>
<li><a
href="e9343db97e"><code>e9343db</code></a>
Bumps form-data (<a
href="https://redirect.github.com/actions/setup-java/issues/887">#887</a>)</li>
<li><a
href="ae2b61dbc6"><code>ae2b61d</code></a>
Bump undici from 5.28.5 to 5.29.0 (<a
href="https://redirect.github.com/actions/setup-java/issues/833">#833</a>)</li>
<li><a
href="c190c18feb"><code>c190c18</code></a>
Bump eslint-plugin-jest from 27.9.0 to 29.0.1 (<a
href="https://redirect.github.com/actions/setup-java/issues/730">#730</a>)</li>
<li><a
href="67aec007b3"><code>67aec00</code></a>
Fix: prevent default installation of JetBrains pre-releases (<a
href="https://redirect.github.com/actions/setup-java/issues/859">#859</a>)</li>
<li><a
href="ebb356cc4e"><code>ebb356c</code></a>
Improve Error Handling for Setup-Java Action to Help Debug Intermittent
Failu...</li>
<li><a
href="f4f1212c88"><code>f4f1212</code></a>
Update publish-immutable-actions.yml (<a
href="https://redirect.github.com/actions/setup-java/issues/798">#798</a>)</li>
<li>See full diff in <a
href="c5195efecf...dded088883">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-java&package-manager=github_actions&previous-version=4.7.1&new-version=5.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 12:28:22 +01:00
dependabot[bot]
58ca41e5c5
build(deps): bump actions/checkout from 4.3.0 to 5.0.0 (#4194)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.3.0
to 5.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
<li>Prepare v5.0.0 release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2238">actions/checkout#2238</a></li>
</ul>
<h2>⚠️ Minimum Compatible Runner Version</h2>
<p><strong>v2.327.1</strong><br />
<a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<p>Make sure your runner is updated to this version or newer to use this
release.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4...v5.0.0">https://github.com/actions/checkout/compare/v4...v5.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>V5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>V4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>v4.2.0</h2>
<ul>
<li>Add Ref and Commit outputs by <a
href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
<li>Dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a
href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>,
<a
href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>v4.1.7</h2>
<ul>
<li>Bump the minor-npm-dependencies group across 1 directory with 4
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li>
<li>Bump actions/checkout from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li>
<li>Check out other refs/* by commit by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
<li>Pin actions/checkout's own workflows to a known, good, stable
version. by <a href="https://github.com/jww3"><code>@​jww3</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1776">actions/checkout#1776</a></li>
</ul>
<h2>v4.1.6</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
</ul>
<h2>v4.1.5</h2>
<ul>
<li>Update NPM dependencies by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
<li>README: Suggest <code>user.email</code> to be
<code>41898282+github-actions[bot]@users.noreply.github.com</code> by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1707">actions/checkout#1707</a></li>
</ul>
<h2>v4.1.4</h2>
<ul>
<li>Disable <code>extensions.worktreeConfig</code> when disabling
<code>sparse-checkout</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1692">actions/checkout#1692</a></li>
<li>Add dependabot config by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1688">actions/checkout#1688</a></li>
<li>Bump the minor-actions-dependencies group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1693">actions/checkout#1693</a></li>
<li>Bump word-wrap from 1.2.3 to 1.2.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1643">actions/checkout#1643</a></li>
</ul>
<h2>v4.1.3</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="08c6903cd8"><code>08c6903</code></a>
Prepare v5.0.0 release (<a
href="https://redirect.github.com/actions/checkout/issues/2238">#2238</a>)</li>
<li><a
href="9f265659d3"><code>9f26565</code></a>
Update actions checkout to use node 24 (<a
href="https://redirect.github.com/actions/checkout/issues/2226">#2226</a>)</li>
<li>See full diff in <a
href="08eba0b27e...08c6903cd8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.3.0&new-version=5.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 12:27:59 +01:00
Anthony Stirling
bf90f4b1da
feat: add annotation option to PDF to image (#4365)
## Summary
- add optional flag to include PDF annotations when converting to images
- expose annotation option via API model and UI checkbox
- add translation for annotation option

## Testing
- `./gradlew spotlessApply`
- `./gradlew build`


------
https://chatgpt.com/codex/tasks/task_b_68b7fa1d5a1c83288342244a0ec85e9d
2025-09-04 12:21:29 +01:00
dependabot[bot]
7e276e8406
build(deps): bump github/codeql-action from 3.29.11 to 3.30.0 (#4355)
Bumps [github/codeql-action](https://github.com/github/codeql-action)
from 3.29.11 to 3.30.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action's
releases</a>.</em></p>
<blockquote>
<h2>v3.30.0</h2>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>3.30.0 - 01 Sep 2025</h2>
<p>No user facing changes.</p>
<p>See the full <a
href="https://github.com/github/codeql-action/blob/v3.30.0/CHANGELOG.md">CHANGELOG.md</a>
for more information.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>3.30.0 - 01 Sep 2025</h2>
<p>No user facing changes.</p>
<h2>3.29.11 - 21 Aug 2025</h2>
<ul>
<li>Update default CodeQL bundle version to 2.22.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3044">#3044</a></li>
</ul>
<h2>3.29.10 - 18 Aug 2025</h2>
<p>No user facing changes.</p>
<h2>3.29.9 - 12 Aug 2025</h2>
<p>No user facing changes.</p>
<h2>3.29.8 - 08 Aug 2025</h2>
<ul>
<li>Fix an issue where the Action would autodetect unsupported languages
such as HTML. <a
href="https://redirect.github.com/github/codeql-action/pull/3015">#3015</a></li>
</ul>
<h2>3.29.7 - 07 Aug 2025</h2>
<p>This release rolls back 3.29.6 to address issues with language
autodetection. It is identical to 3.29.5.</p>
<h2>3.29.6 - 07 Aug 2025</h2>
<ul>
<li>The <code>cleanup-level</code> input to the <code>analyze</code>
Action is now deprecated. The CodeQL Action has written a limited amount
of intermediate results to the database since version 2.2.5, and now
automatically manages cleanup. <a
href="https://redirect.github.com/github/codeql-action/pull/2999">#2999</a></li>
<li>Update default CodeQL bundle version to 2.22.3. <a
href="https://redirect.github.com/github/codeql-action/pull/3000">#3000</a></li>
</ul>
<h2>3.29.5 - 29 Jul 2025</h2>
<ul>
<li>Update default CodeQL bundle version to 2.22.2. <a
href="https://redirect.github.com/github/codeql-action/pull/2986">#2986</a></li>
</ul>
<h2>3.29.4 - 23 Jul 2025</h2>
<p>No user facing changes.</p>
<h2>3.29.3 - 21 Jul 2025</h2>
<p>No user facing changes.</p>
<h2>3.29.2 - 30 Jun 2025</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2d92b76c45"><code>2d92b76</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3067">#3067</a>
from github/update-v3.30.0-92eada825</li>
<li><a
href="390daafd7d"><code>390daaf</code></a>
Update changelog for v3.30.0</li>
<li><a
href="92eada825a"><code>92eada8</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3033">#3033</a>
from github/mbg/ci/rollback-release</li>
<li><a
href="872a6a41e9"><code>872a6a4</code></a>
Add <code>pull-requests: write</code> permission</li>
<li><a
href="9389ce0cc4"><code>9389ce0</code></a>
Merge remote-tracking branch 'origin/main' into
mbg/ci/rollback-release</li>
<li><a
href="02ab253bd2"><code>02ab253</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3054">#3054</a>
from github/henrymercer/bundle</li>
<li><a
href="b06d325850"><code>b06d325</code></a>
Add draft release URL to job summary</li>
<li><a
href="43d629cdfd"><code>43d629c</code></a>
Use <code>argparse</code> in <code>rollback_changelog.py</code></li>
<li><a
href="8f01f5d429"><code>8f01f5d</code></a>
Apply suggestions from code review</li>
<li><a
href="3e493e72f7"><code>3e493e7</code></a>
Remove <code>removeNPMAbsolutePaths</code></li>
<li>Additional commits viewable in <a
href="3c3833e0f8...2d92b76c45">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3.29.11&new-version=3.30.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 12:04:22 +01:00
dependabot[bot]
18e2078b8b
build(deps): bump org.springdoc:springdoc-openapi-starter-webmvc-ui from 2.8.11 to 2.8.12 (#4356)
Bumps
[org.springdoc:springdoc-openapi-starter-webmvc-ui](https://github.com/springdoc/springdoc-openapi)
from 2.8.11 to 2.8.12.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/springdoc/springdoc-openapi/releases">org.springdoc:springdoc-openapi-starter-webmvc-ui's
releases</a>.</em></p>
<blockquote>
<h2>springdoc-openapi v2.8.12 released!</h2>
<h3>Changed</h3>
<ul>
<li>Upgrade swagger-ui to v5.28.0</li>
<li>Upgrade commons-lang3 to v3.18.0</li>
</ul>
<h3>Fixed</h3>
<ul>
<li><a
href="https://redirect.github.com/springdoc/springdoc-openapi/issues/3073">#3073</a>
- Duplicate key class Parameter when documenting two GET methods with
same path and PathVariable.</li>
<li><a
href="https://redirect.github.com/springdoc/springdoc-openapi/issues/3071">#3071</a>
- <a
href="https://github.com/io"><code>@​io</code></a>.swagger.v3.oas.annotations.parameters.RequestBody
does not work well with <a
href="https://github.com/RequestPart"><code>@​RequestPart</code></a></li>
<li><a
href="https://redirect.github.com/springdoc/springdoc-openapi/issues/3066">#3066</a>
- Parameter is now required after upgrading to springdoc-openapi
2.8.10</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/springdoc/springdoc-openapi/compare/v2.8.11...v2.8.12">https://github.com/springdoc/springdoc-openapi/compare/v2.8.11...v2.8.12</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/springdoc/springdoc-openapi/blob/main/CHANGELOG.md">org.springdoc:springdoc-openapi-starter-webmvc-ui's
changelog</a>.</em></p>
<blockquote>
<h2>[2.8.12] - 2025-09-01</h2>
<h3>Changed</h3>
<ul>
<li>Upgrade swagger-ui to v5.28.0</li>
<li>Upgrade commons-lang3 to v3.18.0</li>
</ul>
<h3>Fixed</h3>
<ul>
<li><a
href="https://redirect.github.com/springdoc/springdoc-openapi/issues/3073">#3073</a>
- Duplicate key class Parameter when documenting two GET methods with
same path and PathVariable.</li>
<li><a
href="https://redirect.github.com/springdoc/springdoc-openapi/issues/3071">#3071</a>
- <a
href="https://github.com/io"><code>@​io</code></a>.swagger.v3.oas.annotations.parameters.RequestBody
does not work well with <a
href="https://github.com/RequestPart"><code>@​RequestPart</code></a></li>
<li><a
href="https://redirect.github.com/springdoc/springdoc-openapi/issues/3066">#3066</a>
- Parameter is now required after upgrading to springdoc-openapi
2.8.10</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="493cc68492"><code>493cc68</code></a>
[maven-release-plugin] prepare release v2.8.12</li>
<li><a
href="8ef9eb8199"><code>8ef9eb8</code></a>
CHANGELOG.md update</li>
<li><a
href="bc4ba682eb"><code>bc4ba68</code></a>
upgrade commons-lang3 to v3.18.0</li>
<li><a
href="8741823941"><code>8741823</code></a>
upgrade swagger-ui to v5.28.0</li>
<li><a
href="8d85e0e445"><code>8d85e0e</code></a>
Duplicate key class Parameter when documenting two GET methods with same
path...</li>
<li><a
href="4d505d62dd"><code>4d505d6</code></a>
<a
href="https://github.com/io"><code>@​io</code></a>.swagger.v3.oas.annotations.parameters.RequestBody
does not work well with...</li>
<li><a
href="3190ae2c7f"><code>3190ae2</code></a>
Parameter is now required after upgrading to springdoc-openapi 2.8.10.
Fixes ...</li>
<li><a
href="74dcd4f845"><code>74dcd4f</code></a>
[maven-release-plugin] prepare for next development iteration</li>
<li>See full diff in <a
href="https://github.com/springdoc/springdoc-openapi/compare/v2.8.11...v2.8.12">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.springdoc:springdoc-openapi-starter-webmvc-ui&package-manager=gradle&previous-version=2.8.11&new-version=2.8.12)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 12:03:59 +01:00
124 changed files with 2050 additions and 647 deletions

View File

@ -30,3 +30,9 @@ project: &project
- frontend/**
- docker/**
- testing/**
frontend: &frontend
- frontend/**
- .github/workflows/testdriver.yml
- testing/**
- docker/**

View File

@ -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"

View File

@ -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]'

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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
View File

@ -200,3 +200,6 @@ id_ed25519.pub
# node_modules
node_modules/
# weasyPrint
**/LOCAL_APPDATA_FONTCONFIG_CACHE/**

View File

@ -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

View File

@ -120,14 +120,14 @@ Stirling-PDF currently supports 40 languages!
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![62%](https://geps.dev/progress/62) |
| Basque (Euskara) (eu_ES) | ![36%](https://geps.dev/progress/36) |
| Bulgarian (Български) (bg_BG) | ![68%](https://geps.dev/progress/68) |
| Catalan (Català) (ca_CA) | ![68%](https://geps.dev/progress/68) |
| Catalan (Català) (ca_CA) | ![67%](https://geps.dev/progress/67) |
| Croatian (Hrvatski) (hr_HR) | ![60%](https://geps.dev/progress/60) |
| Czech (Česky) (cs_CZ) | ![70%](https://geps.dev/progress/70) |
| Danish (Dansk) (da_DK) | ![61%](https://geps.dev/progress/61) |
| Dutch (Nederlands) (nl_NL) | ![60%](https://geps.dev/progress/60) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![89%](https://geps.dev/progress/89) |
| French (Français) (fr_FR) | ![88%](https://geps.dev/progress/88) |
| German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) |
| Greek (Ελληνικά) (el_GR) | ![67%](https://geps.dev/progress/67) |
| Hindi (हिंदी) (hi_IN) | ![67%](https://geps.dev/progress/67) |
@ -135,12 +135,12 @@ Stirling-PDF currently supports 40 languages!
| Indonesian (Bahasa Indonesia) (id_ID) | ![62%](https://geps.dev/progress/62) |
| Irish (Gaeilge) (ga_IE) | ![68%](https://geps.dev/progress/68) |
| Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) |
| Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) |
| Japanese (日本語) (ja_JP) | ![92%](https://geps.dev/progress/92) |
| Korean (한국어) (ko_KR) | ![67%](https://geps.dev/progress/67) |
| Norwegian (Norsk) (no_NB) | ![66%](https://geps.dev/progress/66) |
| Persian (فارسی) (fa_IR) | ![64%](https://geps.dev/progress/64) |
| Polish (Polski) (pl_PL) | ![72%](https://geps.dev/progress/72) |
| Portuguese (Português) (pt_PT) | ![69%](https://geps.dev/progress/69) |
| Portuguese (Português) (pt_PT) | ![68%](https://geps.dev/progress/68) |
| Portuguese Brazilian (Português) (pt_BR) | ![76%](https://geps.dev/progress/76) |
| Romanian (Română) (ro_RO) | ![57%](https://geps.dev/progress/57) |
| Russian (Русский) (ru_RU) | ![88%](https://geps.dev/progress/88) |
@ -152,9 +152,9 @@ Stirling-PDF currently supports 40 languages!
| Swedish (Svenska) (sv_SE) | ![65%](https://geps.dev/progress/65) |
| Thai (ไทย) (th_TH) | ![59%](https://geps.dev/progress/59) |
| Tibetan (བོད་ཡིག་) (bo_CN) | ![65%](https://geps.dev/progress/65) |
| Traditional Chinese (繁體中文) (zh_TW) | ![97%](https://geps.dev/progress/97) |
| Turkish (Türkçe) (tr_TR) | ![80%](https://geps.dev/progress/80) |
| Ukrainian (Українська) (uk_UA) | ![71%](https://geps.dev/progress/71) |
| Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) |
| Turkish (Türkçe) (tr_TR) | ![99%](https://geps.dev/progress/99) |
| Ukrainian (Українська) (uk_UA) | ![70%](https://geps.dev/progress/70) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![57%](https://geps.dev/progress/57) |
| Malayalam (മലയാളം) (ml_IN) | ![73%](https://geps.dev/progress/73) |

View File

@ -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'
}

View File

@ -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 =

View File

@ -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 {

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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();
}

View File

@ -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.
*

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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
}
}
}

View File

@ -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
View File

@ -170,6 +170,10 @@ out/
*.jks
*.asc
# test-cert
!**/test/resources/certs/test-cert.*
!**/test/resources/certs/test-key.*
# SSH Keys
*.pub
*.priv

View File

@ -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;
}
}

View File

@ -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("/")) {

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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;
private boolean isUnoconvertAvailable() {
return endpointConfiguration.isGroupEnabled("Unoconvert")
|| endpointConfiguration.isGroupEnabled("Python");
}
public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
// Check for valid file extension
// Check for valid file extension and sanitize filename
String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
if (originalFilename == null
|| !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) {
throw new IllegalArgumentException("Invalid file extension");
if (originalFilename == null || originalFilename.isBlank()) {
throw new IllegalArgumentException("Missing original filename");
}
// Save the uploaded file to a temporary location
Path tempInputFile =
Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
// 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 =
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
.runCommandWithOutputHandling(command);
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());
// Read the converted PDF file
return tempOutputFile.toFile();
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());
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());
}
}
}
}

View File

@ -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;

View File

@ -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;
// Horizontal alignment: left = anchor, center = centered, right = right-aligned
float x =
switch (col) {
case 1 -> leftX;
case 2 -> midX - textWidth / 2f;
default -> rightX - textWidth;
};
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
};
// 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;
};
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
};
}
PDPageContentStream contentStream =
try (PDPageContentStream contentStream =
new PDPageContentStream(
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
contentStream.beginText();
contentStream.setFont(currentFont, fontSize);
contentStream.newLineAtOffset(x, y);
contentStream.showText(text);
contentStream.endText();
contentStream.close();
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();
}
pageNumber++;
}

View File

@ -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;
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;
}
float marginFactor =
switch (request.getCustomMargin().toLowerCase()) {
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);
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;
}
resourceDir =
switch (alphabet) {
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 {

View File

@ -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)) {

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 + "]";

View File

@ -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;
}

View File

@ -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,"

View File

@ -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;

View File

@ -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)")

View File

@ -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");

View File

@ -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"));

View File

@ -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<>();

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,138 +1,138 @@
###########
# 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
lang.afr=Afrikaanca
lang.amh=Amharca
lang.ara=Arapça
lang.asm=Assamca
lang.aze=Azerice
lang.aze_cyrl=Azerice (Kiril)
lang.bel=Beyaz Rusça (Belarusça)
lang.ben=Bengalce
lang.bod=Tibetçe
lang.bos=Boşnakça
lang.bre=Bretonca
lang.bul=Bulgarca
lang.cat=Katalanca
lang.ceb=Cebuano
lang.ces=Çekçe
lang.chi_sim=Çince (Basitleştirilmiş)
lang.chi_sim_vert=Çince (Basitleştirilmiş, Dikey)
lang.chi_tra=Çince (Geleneksel)
lang.chi_tra_vert=Çince (Geleneksel, Dikey)
lang.chr=Çerokice
lang.cos=Korsikaca
lang.cym=Gallerce (Galce)
lang.dan=Danca
lang.dan_frak=Danca (Fraktur)
lang.deu=Almanca
lang.deu_frak=Almanca (Fraktur)
lang.div=Maldivce (Divehi)
lang.dzo=Dzongkha
lang.ell=Yunanca
lang.eng=İngilizce
lang.enm=İngilizce, Orta Çağ (1100-1500)
lang.epo=Esperanto
lang.equ=Matematik / denklem tanıma modülü
lang.est=Estonca
lang.eus=Baskça
lang.fao=Faroece
lang.fas=Farsça
lang.fil=Filipince
lang.fin=Fince
lang.fra=Fransızca
lang.frk=Frankça
lang.frm=Fransızca, Orta Çağ (yaklaşık 1400-1600)
lang.fry=Batı Frizce
lang.gla=İskoç Galcesi
lang.gle=İrlandaca
lang.glg=Galiçyaca
lang.grc=Antik Yunanca
lang.guj=Gujaratça
lang.hat=Haiti Creole
lang.heb=İbranice
lang.hin=Hintçe
lang.hrv=Hırvatça
lang.hun=Macarca
lang.hye=Ermenice
lang.iku=İnuktitut
lang.ind=Endonezce
lang.isl=İzlandaca
lang.ita=İtalyanca
lang.ita_old=İtalyanca (Eski)
lang.jav=Cava dili
lang.jpn=Japonca
lang.jpn_vert=Japonca (Dikey)
lang.kan=Kannada
lang.kat=Gürcüce
lang.kat_old=Gürcüce (Eski)
lang.kaz=Kazakça
lang.khm=Merkez Khmer dili
lang.kir=Kırgızca
lang.kmr=Kuzey Kürtçesi
lang.kor=Korece
lang.kor_vert=Korece (Dikey)
lang.lao=Laosça
lang.lat=Latince
lang.lav=Letonca
lang.lit=Litvanca
lang.ltz=Lüksemburgca
lang.mal=Malayalamca
lang.mar=Marathi
lang.mkd=Makedonca
lang.mlt=Maltaca
lang.mon=Moğolca
lang.mri=Maorice
lang.msa=Malayca
lang.mya=Birmanca (Burma)
lang.nep=Nepalce
lang.nld=Hollandaca; Flamanca
lang.nor=Norveççe
lang.oci=Oksitanca (1500 sonrası)
lang.ori=Oriya
lang.osd=Yönlendirme ve yazı tipi algılama modülü
lang.pan=Pencapça
lang.pol=Lehçe (Polonyaca)
lang.por=Portekizce
lang.pus=Peştuca
lang.que=Keçuva dili
lang.ron=Rumence, Moldovca
lang.rus=Rusça
lang.san=Sanskritçe
lang.sin=Seylanca (Sinhala)
lang.slk=Slovakça
lang.slk_frak=Slovakça (Fraktur)
lang.slv=Slovence
lang.snd=Sindhice
lang.spa=İspanyolca
lang.spa_old=İspanyolca (Eski)
lang.sqi=Arnavutça
lang.srp=Sırpça
lang.srp_latn=Sırpça (Latin alfabesiyle)
lang.sun=Sundaca
lang.swa=Svahili dili
lang.swe=İsveççe
lang.syr=Süryanice
lang.tam=Tamilce
lang.tat=Tatarca
lang.tel=Telugu
lang.tgk=Tacikçe
lang.tgl=Tagalog
lang.tha=Tayca
lang.tir=Tigrinya
lang.ton=Tonga dili (Tonga Adaları)
lang.tur=Türkçe
lang.uig=Uygurca
lang.ukr=Ukraynaca
lang.urd=Urduca
lang.uzb=Özbekçe
lang.uzb_cyrl=Özbekçe (Kiril)
lang.vie=Vietnamca
lang.yid=Yidiş
lang.afr=Afrikaanca
lang.amh=Amharca
lang.ara=Arapça
lang.asm=Assamca
lang.aze=Azerice
lang.aze_cyrl=Azerice (Kiril)
lang.bel=Beyaz Rusça (Belarusça)
lang.ben=Bengalce
lang.bod=Tibetçe
lang.bos=Boşnakça
lang.bre=Bretonca
lang.bul=Bulgarca
lang.cat=Katalanca
lang.ceb=Cebuano
lang.ces=Çekçe
lang.chi_sim=Çince (Basitleştirilmiş)
lang.chi_sim_vert=Çince (Basitleştirilmiş, Dikey)
lang.chi_tra=Çince (Geleneksel)
lang.chi_tra_vert=Çince (Geleneksel, Dikey)
lang.chr=Çerokice
lang.cos=Korsikaca
lang.cym=Gallerce (Galce)
lang.dan=Danca
lang.dan_frak=Danca (Fraktur)
lang.deu=Almanca
lang.deu_frak=Almanca (Fraktur)
lang.div=Maldivce (Divehi)
lang.dzo=Dzongkha
lang.ell=Yunanca
lang.eng=İngilizce
lang.enm=İngilizce, Orta Çağ (1100-1500)
lang.epo=Esperanto
lang.equ=Matematik / denklem tanıma modülü
lang.est=Estonca
lang.eus=Baskça
lang.fao=Faroece
lang.fas=Farsça
lang.fil=Filipince
lang.fin=Fince
lang.fra=Fransızca
lang.frk=Frankça
lang.frm=Fransızca, Orta Çağ (yaklaşık 1400-1600)
lang.fry=Batı Frizce
lang.gla=İskoç Galcesi
lang.gle=İrlandaca
lang.glg=Galiçyaca
lang.grc=Antik Yunanca
lang.guj=Gujaratça
lang.hat=Haiti Creole
lang.heb=İbranice
lang.hin=Hintçe
lang.hrv=Hırvatça
lang.hun=Macarca
lang.hye=Ermenice
lang.iku=İnuktitut
lang.ind=Endonezce
lang.isl=İzlandaca
lang.ita=İtalyanca
lang.ita_old=İtalyanca (Eski)
lang.jav=Cava dili
lang.jpn=Japonca
lang.jpn_vert=Japonca (Dikey)
lang.kan=Kannada
lang.kat=Gürcüce
lang.kat_old=Gürcüce (Eski)
lang.kaz=Kazakça
lang.khm=Merkez Khmer dili
lang.kir=Kırgızca
lang.kmr=Kuzey Kürtçesi
lang.kor=Korece
lang.kor_vert=Korece (Dikey)
lang.lao=Laosça
lang.lat=Latince
lang.lav=Letonca
lang.lit=Litvanca
lang.ltz=Lüksemburgca
lang.mal=Malayalamca
lang.mar=Marathi
lang.mkd=Makedonca
lang.mlt=Maltaca
lang.mon=Moğolca
lang.mri=Maorice
lang.msa=Malayca
lang.mya=Birmanca (Burma)
lang.nep=Nepalce
lang.nld=Hollandaca; Flamanca
lang.nor=Norveççe
lang.oci=Oksitanca (1500 sonrası)
lang.ori=Oriya
lang.osd=Yönlendirme ve yazı tipi algılama modülü
lang.pan=Pencapça
lang.pol=Lehçe (Polonyaca)
lang.por=Portekizce
lang.pus=Peştuca
lang.que=Keçuva dili
lang.ron=Rumence, Moldovca
lang.rus=Rusça
lang.san=Sanskritçe
lang.sin=Seylanca (Sinhala)
lang.slk=Slovakça
lang.slk_frak=Slovakça (Fraktur)
lang.slv=Slovence
lang.snd=Sindhice
lang.spa=İspanyolca
lang.spa_old=İspanyolca (Eski)
lang.sqi=Arnavutça
lang.srp=Sırpça
lang.srp_latn=Sırpça (Latin alfabesiyle)
lang.sun=Sundaca
lang.swa=Svahili dili
lang.swe=İsveççe
lang.syr=Süryanice
lang.tam=Tamilce
lang.tat=Tatarca
lang.tel=Telugu
lang.tgk=Tacikçe
lang.tgl=Tagalog
lang.tha=Tayca
lang.tir=Tigrinya
lang.ton=Tonga dili (Tonga Adaları)
lang.tur=Türkçe
lang.uig=Uygurca
lang.ukr=Ukraynaca
lang.urd=Urduca
lang.uzb=Özbekçe
lang.uzb_cyrl=Özbekçe (Kiril)
lang.vie=Vietnamca
lang.yid=Yidiş
lang.yor=Yoruba
addPageNumbers.fontSize=Font Büyüklüğü
@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
},
{

View File

@ -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