Compare commits

..
149 Commits
Author SHA1 Message Date
Anthony Stirling c480dea842 removePage 2025-08-20 13:21:37 +01:00
Anthony Stirling a9f3e197d4 tooltip 2025-08-20 12:53:02 +01:00
Anthony Stirling f1cb8ce5b3 V2 Repair tool (#4234)
# 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-08-19 18:00:50 +01:00
EthanHealy01 8f32082145 Feature/v2/all tools sidebar (#4151)
# Description of Changes

- Added the all tools sidebar
- Added a TextFit component that shrinks text to fit containers
- Added a TopToolIcon on the nav, that animates down to give users
feedback on what tool is selected
- Added the baseToolRegistry, to replace the old pattern of listing
tools, allowing us to clean up the ToolRegistry code
- Fixed Mantine light/dark theme race condition 
- General styling tweaks

---

## 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-08-19 13:31:09 +01:00
ConnorYohandConnor Yoh c1b7911518 Feature/v2/watermark (#4215)
Add watermark feature

Auto scroll on review

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-19 10:31:44 +01:00
James Brunton acbebd67a3 Add Remove Password UI into V2 (#4214)
# Description of Changes
- Add UI for Remove Password tool
- Fix more translation warnings that were being thrown in the console
- Add an encrypted PDF thumbnail and refactor thumbnail generation code
2025-08-18 15:26:29 +01:00
4c17c520d7 V2 results flow (#4196)
Better tool flow for reusability
Pinning 
Styling of tool flow
consumption of files after tooling

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2025-08-15 14:43:30 +01:00
1468df3e21 JWT Auth into V2 (#4187)
Merging JWT feature from main into V2

## 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.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Co-authored-by: Ethan <ethan@MacBook-Pro.local>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
2025-08-15 14:13:45 +01:00
7e60eb40b4 Bug/v2/reduce console pollution (#4212)
# Description of Changes

<!--

Fixes the issues with I18 that were causing lots of console errors.
Fixes mime type error

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.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
Co-authored-by: Dario Ghunney Ware <dariogware@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Co-authored-by: Ethan <ethan@MacBook-Pro.local>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
2025-08-15 13:15:21 +01:00
Anthony Stirling 129e4d00e9 Seo v2 add home (#4211)
# 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-08-14 16:36:24 +01:00
Anthony Stirling 8149cb157c SEO init (#4197)
# Description of Changes
This pull request introduces dynamic document meta tag management and
improves URL routing and tool metadata handling in the frontend. The
most significant changes are the addition of a custom hook to update
document meta tags (including OpenGraph tags) based on the selected
tool, enhancements to the tool selection context for better URL
synchronization, and enrichment of the `Tool` type and tool registry
with more metadata.

**Dynamic document meta management:**

* Added a new `useDocumentMeta` hook that updates the page's `<title>`,
description, and OpenGraph meta tags based on the currently selected
tool, and restores the original values on cleanup.
(`frontend/src/hooks/useDocumentMeta.ts`)
* Integrated `useDocumentMeta` into `HomePageContent` so the document's
meta tags dynamically reflect the selected tool's metadata, improving
SEO and social sharing. (`frontend/src/pages/HomePage.tsx`)
[[1]](diffhunk://#diff-85c26b21681286c20e97a26a4912f0b91812776c9d4d0c54aa541fded2565c7eR2-R8)
[[2]](diffhunk://#diff-85c26b21681286c20e97a26a4912f0b91812776c9d4d0c54aa541fded2565c7eR17)
[[3]](diffhunk://#diff-85c26b21681286c20e97a26a4912f0b91812776c9d4d0c54aa541fded2565c7eR28-R37)

**Tool metadata and context improvements:**

* Enhanced the `Tool` type and tool registry to include `title` and
`description` fields, which are now translated and used throughout the
UI and meta tags. (`frontend/src/types/tool.ts`,
`frontend/src/hooks/useToolManagement.tsx`)
[[1]](diffhunk://#diff-0b557df7bd27ac90cd2f925ddd8ef8096ea2decfaee9a5c12a94dc7a03c64bfaR46)
[[2]](diffhunk://#diff-57f8a6b3e75ecaec10ad445b01afe8fccc376af6f8ad4d693c68cf98e8863273L116-R118)
* Updated the `ToolWorkflowContext` to use the new `Tool` type for
`selectedTool`, replacing the previous `ToolConfiguration` type.
(`frontend/src/contexts/ToolWorkflowContext.tsx`)
[[1]](diffhunk://#diff-9b36e2c06dddbcfba6cb66fd0b303b7860f88ca8b562bb2534af1ab50390d385L6-R8)
[[2]](diffhunk://#diff-9b36e2c06dddbcfba6cb66fd0b303b7860f88ca8b562bb2534af1ab50390d385L72-R72)

**URL routing and synchronization:**

* Implemented logic in `ToolWorkflowContext` to synchronize the selected
tool with the browser URL, initialize tool selection from the URL on
load, and handle browser navigation (back/forward) for tool selection.
(`frontend/src/contexts/ToolWorkflowContext.tsx`)
---

## 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-08-14 15:52:38 +01:00
James Brunton 22db7bec76 Improve titles, headers and tooltips in Add Password and Change Permissions (#4210)
# Description of Changes
Improve titles, headers and tooltips in Add Password and Change
Permissions
2025-08-14 15:31:53 +01:00
James BruntonandJames ecf30d1028 Implement 'Add Password' and 'Change Permissions' tools in V2 (#4195)
# Description of Changes
Implement Add Password and Change Permissions tools in V2 (both in one
because Change Permissions is a fake endpoint which just calls Add
Password behind the scenes).

---------

Co-authored-by: James <james@crosscourtanalytics.com>
2025-08-14 14:27:23 +01:00
Anthony Stirling 0ea4410dd3 Update manifest.json (#4203)
# 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-08-13 18:29:25 +01:00
8eeb4c148c Add Sanitize UI (#4123)
# Description of Changes

Implementation of Sanitize UI for V2.

Also removes parameter validation from standard tool hooks because the
logic would have to be duplicated between parameter handling and
operation hooks, and the nicer workflow is for the tools to reject using
the Go button if the validation fails, rather than the operation hook
checking it, since that can't appear in the UI.

Co-authored-by: James <james@crosscourtanalytics.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2025-08-12 16:05:59 +01:00
James BruntonandJames adf6feea27 Neaten garbage collection code (#4171)
# Description of Changes
Slight tweaks to type checking code merged in #4126.

Co-authored-by: James <james@crosscourtanalytics.com>
2025-08-12 08:56:31 +00:00
Anthony Stirling 299e0b2475 cleanurl fix 2025-08-12 09:51:13 +01:00
Anthony StirlingandCopilot 768ece6921 Remove backend UI (#4023)
# 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.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-11 12:27:18 +01:00
Anthony Stirling 20245c67e0 Merge pull request #4162 from Stirling-Tools/mainClone
V2 main merge upbranch
2025-08-11 12:26:31 +01:00
Anthony Stirling c5328d2aee Update build.yml 2025-08-11 10:30:05 +01:00
Anthony Stirling 17d9993a53 fix merge issues 2025-08-11 10:22:47 +01:00
James BruntonandJames af5a9d1ae1 Enforce type checking in CI (#4126)
# Description of Changes
Currently, the `tsconfig.json` file enforces strict type checking, but
nothing in CI checks that the code is actually correctly typed. [Vite
only transpiles TypeScript
code](https://vite.dev/guide/features.html#transpile-only) so doesn't
ensure that the TS code we're running is correct.

This PR adds running of the type checker to CI and fixes the type errors
that have already crept into the codebase.

Note that many of the changes I've made to 'fix the types' are just
using `any` to disable the type checker because the code is under too
much churn to fix anything properly at the moment. I still think
enabling the type checker now is the best course of action though
because otherwise we'll never be able to fix all of them, and it should
at least help us not break things when adding new code.

Co-authored-by: James <james@crosscourtanalytics.com>
2025-08-11 09:16:16 +01:00
Anthony Stirling af69256abb docker fixes 2025-08-10 18:15:05 +01:00
Anthony Stirling 7c49379a70 remove old dockers 2025-08-10 17:49:16 +01:00
Anthony Stirling c1082d9e42 Merge remote-tracking branch 'origin/V2' into mainClone 2025-08-10 17:48:44 +01:00
stirlingbot[bot] 979f302277 🌐 Sync Translations + Update README Progress Table (#4159)
### 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-08-09 15:33:08 +01:00
Balázs Szücs dd0bf194cd Update Hungarian translation for new update related strings (#4152)
# 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-08-09 15:31:28 +01:00
Ludy 05b5771c89 fix(saml): correct ClassPathResource handling for IdP metadata and add null-guard for privateKey (#4157)
## Description of Changes

**What was changed**
- In `getIdpMetadataUri()`, use
`idpMetadataUri.substring("classpath:".length())` so the `classpath:`
scheme (including the colon) is stripped correctly before creating the
`ClassPathResource`.
- In `getPrivateKey()`, add a null check (`if (privateKey == null)
return null;`) to avoid a potential `NullPointerException` when the
property is unset.

**Why the change was made**
- The previous substring used `"classpath".length()` (without the
colon), leaving a leading `:` in the path (e.g., `:/saml/idp.xml`) which
breaks `ClassPathResource` resolution and can prevent SAML bootstrapping
when `idpMetadataUri` uses the `classpath:` scheme.
- The null-guard aligns the method with defensive coding practices and
prevents runtime errors when no private key is configured.


---

## 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-08-09 15:09:50 +01:00
Anthony Stirling 299ce03dda Update CODEOWNERS (#4158)
# 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-08-09 15:09:26 +01:00
Anthony Stirling 5e01b15d3c Update .files.yaml for V2 (#4156)
# 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-08-09 12:03:24 +01:00
stirlingbot[bot] 3938a07c13 🌐 Sync Translations + Update README Progress Table (#4155)
### 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-08-08 23:13:33 +01:00
albanobattistella e8b5ae0474 Update messages_it_IT.properties (#4154)
# 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-08-08 23:07:20 +01:00
Reece BrowneandReece Browne 507ad1dc61 Feature/v2/shared tool hooks (#4134)
# 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.

---------

Co-authored-by: Reece Browne <you@example.com>
2025-08-08 16:01:56 +01:00
b45d3a43d4 V2 Restructure homepage (#4138)
Component Extraction & Context Refactor - Summary

  🔧 What We Did

- Extracted HomePage's 286-line monolithic component into focused parts
  - Created ToolPanel (105 lines) for tool selection UI
  - Created Workbench (203 lines) for view management
  - Created ToolWorkflowContext (220 lines) for centralized state
  - Reduced HomePage to 60 lines of provider setup
  - Eliminated all prop drilling - components use contexts directly

  🏆 Why This is Good

- Maintainability: Each component has single purpose, easy
debugging/development
- Architecture: Clean separation of concerns, future features easier to
add
- Code Quality: 105% more lines but organized/purposeful vs tangled
spaghetti code

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2025-08-08 15:56:20 +01:00
stirlingbot[bot] 796873134f Update 3rd Party Licenses (#4122)
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-08-08 15:54:00 +01:00
stirlingbot[bot] 678a9bc463 🤖 format everything with pre-commit by stirlingbot (#4150)
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-08-08 15:53:45 +01:00
James BruntonandJames 7735ea7f48 Enforce 2 space indentation in editorconfig (#4149)
# Description of Changes

Add missing config for JS and TS files to enforce 2 space indentation in
`.editorconfig`

<!--
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)
- [x] 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

- [x] 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)
- [x] 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)

- [x] 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.

Co-authored-by: James <james@crosscourtanalytics.com>
2025-08-08 15:50:59 +01:00
Anthony Stirling 71ac4283b2 PSD (#4146)
# 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-08-08 15:39:47 +01:00
stirlingbot[bot] 6675a8af99 🌐 Sync Translations + Update README Progress Table (#4143)
### 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-08-08 15:37:18 +01:00
stirlingbot[bot] e6a77e83da 🤖 format everything with pre-commit by stirlingbot (#4144)
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-08-08 15:36:58 +01:00
ConnorYohandConnor Yoh 7e3321ee16 Feature/v2/filemanager (#4121)
FileManager Component Overview

Purpose: Modal component for selecting and managing PDF files with
preview capabilities

  Architecture:
- Responsive Layouts: MobileLayout.tsx (stacked) vs DesktopLayout.tsx
(3-column)
- Central State: FileManagerContext handles file operations, selection,
and modal state
  - File Storage: IndexedDB persistence with thumbnail caching

  Key Components:
  - FileSourceButtons: Switch between Recent/Local/Drive sources
  - FileListArea: Scrollable file grid with search functionality
- FilePreview: PDF thumbnails with dynamic shadow stacking (1-2 shadow
pages based on file count)
  - FileDetails: File info card with metadata
  - CompactFileDetails: Mobile-optimized file info layout

  File Flow:
1. Users select source → browse/search files → select multiple files →
preview with navigation → open in
  tools
  2. Files persist across tool switches via FileContext integration
  3. Memory management handles large PDFs (up to 100GB+)

 ```mermaid
 graph TD
      FM[FileManager] --> ML[MobileLayout]
      FM --> DL[DesktopLayout]

      ML --> FSB[FileSourceButtons<br/>Recent/Local/Drive]
      ML --> FLA[FileListArea]
      ML --> FD[FileDetails]

      DL --> FSB
      DL --> FLA
      DL --> FD

      FLA --> FLI[FileListItem]
      FD --> FP[FilePreview]
      FD --> CFD[CompactFileDetails]

  ```

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-08 15:15:09 +01:00
774b500159 get updates advanced (#4124)
# Description of Changes
This pull request introduces a comprehensive update to the application's
update notification and modal system, enhancing both the backend logic
and the user interface for update alerts. The changes include a new
modal dialog for update details, improved internationalization (i18n)
support, dynamic fetching of update information, and context-aware
download links. These improvements make update notifications clearer,
more informative, and tailored to the user's installation type.

**Key changes:**

**1. Update Notification and Modal System Overhaul**
- Added a new modal dialog (`showUpdateModal`) that displays detailed
update information, including current, latest, and latest stable
versions, update priority, breaking changes, migration guides, and a
list of available updates. The modal dynamically fetches and displays
full update details and adapts to dark mode.
([[app/core/src/main/resources/static/js/githubVersion.jsR206-R387](diffhunk://#diff-5a6376050581cc6f1fb0b6266af4d8a3db1332879459afd3a073b274b5ab637aR206-R387)])
- Enhanced the update button logic to reflect update priority visually
(e.g., urgent/normal/minor), store summary data, and trigger the modal
on click.
([[app/core/src/main/resources/static/js/githubVersion.jsL74-R190](diffhunk://#diff-5a6376050581cc6f1fb0b6266af4d8a3db1332879459afd3a073b274b5ab637aL74-R190)])
- Improved the update check process to use a new summary API endpoint
and handle missing or failed update data gracefully.
[[1]](diffhunk://#diff-5a6376050581cc6f1fb0b6266af4d8a3db1332879459afd3a073b274b5ab637aL19-R108)],
[[2]](diffhunk://#diff-5a6376050581cc6f1fb0b6266af4d8a3db1332879459afd3a073b274b5ab637aL74-R190)])

**2. Context-Aware Download Links**
- Introduced `getDownloadUrl()` to generate download links based on the
user's machine type and security configuration, ensuring only relevant
installers or jars are offered.
([[app/core/src/main/resources/static/js/githubVersion.jsL19-R108](diffhunk://#diff-5a6376050581cc6f1fb0b6266af4d8a3db1332879459afd3a073b274b5ab637aL19-R108)])

**3. Internationalization (i18n) Enhancements**
- Added new i18n keys for all update-related modal and notification
strings in `messages_en_GB.properties`.
([[app/core/src/main/resources/messages_en_GB.propertiesR369-R400](diffhunk://#diff-ee1c6999a33498cfa3abba4a384e73a8b8269856899438de80560c965079a9fdR369-R400)])
- Injected all necessary i18n constants into the frontend via
`navbar.html` for use in the modal and notifications.
([[app/core/src/main/resources/templates/fragments/navbar.htmlR14-R51](diffhunk://#diff-e7ef383033ea52a00c96e71d5d2c1ff08829078fa5c84c8e48e1bf8f48861ec6R14-R51)])

**4. General UI and Code Improvements**
- Ensured update button styling is reset before applying new styles and
improved accessibility by hiding the settings modal when the update
modal is shown.
[[1]](diffhunk://#diff-5a6376050581cc6f1fb0b6266af4d8a3db1332879459afd3a073b274b5ab637aR138)],
[[2]](diffhunk://#diff-5a6376050581cc6f1fb0b6266af4d8a3db1332879459afd3a073b274b5ab637aR206-R387)])

These changes collectively provide a more robust, user-friendly, and
maintainable update notification experience.


---

## 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.

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Reece Browne <reecebrowne1995@gmail.com>
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
Co-authored-by: a <a>
2025-08-08 14:19:19 +01:00
Balázs Szücs 65e894870c refactor(eml-to-pdf): Improve readability, maintainability, and overall standards compliance (#4065)
# Description of Changes
refactor(eml-to-pdf): Enhance compliance with PDF/ISO standards and MIME
specifications

This commit refactors the EML-to-PDF conversion utility to improve
standards compliance, implementing requirements from multiple RFCs and
ISO specifications:

### Standards Compliance Implemented:
• **PDF Standards (ISO 32000-1:2008)**: Added PDF version validation in
`attachFilesToPdf()`
  to ensure 1.7+ compatibility for Unicode file embeddings
• **MIME Processing (RFC 2045/2046)**: Implemented case-insensitive MIME
type handling
in `processPartAdvanced()` with `toLowerCase(Locale.ROOT)` normalization
• **Content Encoding (RFC 2047)**: Enhanced `safeMimeDecode()` with
UTF-8→ISO-8859-1
  charset fallback chains for robust header decoding
• **Content-ID Processing (RFC 2392)**: Added proper Content-ID
stripping with
  `replaceAll("[<>]", "")` for embedded image references
• **Multipart Safety (RFC 2046)** (best practice, not compliance
related): Implemented recursion depth limiting (max 10 levels)
• **processMultipartAdvanced()**, setCatalogViewerPreferences used to
set PageMode.USE_ATTACHMENTS, but PDF spec 12.2 (Viewer Preferences)
requires a /ViewerPreferences dictionary for full control (e.g.,
/DisplayDocTitle). Docs suggested setting additional prefs like
/NonFullScreenPageMode to ensure attachments panel opens reliably across
viewers
• **addAttachmentAnnotationToPage**, annotations are set to
/Invisible=true but must remain interactive. PDF spec 12.5.6.15 (File
Attachment Annotations) requires /F flags to control print/view (e.g.,
NoPrint if not printable).

### Technical Improvements:
• **Coordinate System Handling**: Added rotation-aware coordinate
transformations
  in PDF annotation placement following ISO 32000-1 Section 8.3
• **Charset Fallbacks**: Implemented progressive charset detection with
UTF-8
  primary and ISO-8859-1 fallback in MIME decoding
• **Error Resilience**: Enhanced exception handling with specific error
types and
  proper resource cleanup using try-with-resources patterns
• **HTML5 Compliance**: Updated email HTML generation with proper
DOCTYPE and
  charset declarations for browser compatibility

### Security & Robustness:
• **Input Validation**: Added comprehensive null checks and boundary
validation
  throughout attachment and multipart processing
• **XSS Prevention**: All user content now processed through
`escapeHtml()` or
  `CustomHtmlSanitizer` before HTML generation

### Code Quality:
• **Method Signatures**: Updated `processMultipartAdvanced()` to include
depth
  parameter for recursion tracking
• **Switch Expressions**: Modernized switch statements to use Java 17+
arrow syntax
  where applicable
• **Documentation**: Added inline RFC/ISO references for
compliance-critical sections

All changes maintain backward compatibility while significantly
improving standards
adherence. Tested with various EML formats.

No major change. No change in tests. No change in aesthetic of the
resulting PDF. No change change in "user space" (except when user relied
on compliance of aforementioned stuff then a major improvement)

<!--
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
- [ ] 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.
2025-08-08 13:14:57 +01:00
Ludy b6ff1dd7f6 chore: update development configs, formatting tools, and CI enhancements (#4130)
# Description of Changes

- **What was changed**  
- Bumped `java.format.settings.google.version` to **1.28.0** in
`.devcontainer/devcontainer.json` and `.vscode/settings.json`.
- Expanded ignore patterns in `.devcontainer/devcontainer.json` to cover
`app/core/`, `app/common/`, `app/proprietary/` directories.
- Added a new top‐level `.dockerignore` to exclude build artifacts,
virtual environments, logs, OS files, and markdown docs.
- Consolidated EditorConfig YAML globs into `*.{yml,yaml}` to remove
duplication.
- Fixed missing newline in `.github/config/.files.yaml` and added label
metadata (`from_name`/`description`) in `.github/labels.yml`.
  - Updated `build.gradle`:
- Introduced `junitPlatformVersion = "1.12.2"` and replaced hard-coded
launcher versions.
- Applied the `jacoco` plugin across all subprojects and configured
`jacocoTestReport` (XML + HTML).
    - Wire-up `jacocoTestReport` to run after tests.  
- **Why the change was made**  
- Ensure all formatting tools (Google Java Format) stay in sync across
editors and containers.
- Clean up ignore rules to prevent build artifacts and sensitive files
from creeping into images and repos.
- Improve CI visibility by generating code-coverage reports with JaCoCo.
- Keep GitHub configuration files well-formed and enrich label
definitions for automation.

---

## 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-08-08 12:52:51 +01:00
dependabot[bot] bb8edffaab build(deps): bump actions/ai-inference from 1.2.3 to 1.2.4 (#4119)
Bumps [actions/ai-inference](https://github.com/actions/ai-inference)
from 1.2.3 to 1.2.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/ai-inference/releases">actions/ai-inference's
releases</a>.</em></p>
<blockquote>
<h2>v1.2.4</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump <code>@​github/local-action</code> from 3.2.1 to 5.1.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/ai-inference/pull/63">actions/ai-inference#63</a></li>
<li>Bump <code>@​rollup/rollup-linux-x64-gnu</code> from 4.43.0 to
4.45.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/ai-inference/pull/65">actions/ai-inference#65</a></li>
<li>Bump jest and <code>@​types/jest</code> by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/ai-inference/pull/66">actions/ai-inference#66</a></li>
<li>Tidy up package.json by <a
href="https://github.com/maraisr"><code>@​maraisr</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/69">actions/ai-inference#69</a></li>
<li>Moves project to using vitest by <a
href="https://github.com/maraisr"><code>@​maraisr</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/70">actions/ai-inference#70</a></li>
<li>Move some linter files out of the root and use GitHub's shared
prettier config by <a
href="https://github.com/maraisr"><code>@​maraisr</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/72">actions/ai-inference#72</a></li>
<li>chore(deps): bump <code>@​rollup/rollup-linux-x64-gnu</code> from
4.45.1 to 4.46.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/ai-inference/pull/76">actions/ai-inference#76</a></li>
<li>chore(deps): bump actions/publish-action from 0.2.2 to 0.3.0 in the
actions-minor group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/ai-inference/pull/74">actions/ai-inference#74</a></li>
<li>Separate out MCP token by <a
href="https://github.com/sgoedecke"><code>@​sgoedecke</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/83">actions/ai-inference#83</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/ai-inference/compare/v1...v1.2.4">https://github.com/actions/ai-inference/compare/v1...v1.2.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/ai-inference/commit/4b591cc5298ae9428fff72279eced918e444c409"><code>4b591cc</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/ai-inference/issues/83">#83</a>
from actions/sgoedecke/separate-mcp</li>
<li><a
href="https://github.com/actions/ai-inference/commit/ea24ec2ed4aeb6d19b8936f886e8fb5741527467"><code>ea24ec2</code></a>
Update README.md</li>
<li><a
href="https://github.com/actions/ai-inference/commit/b9f9444fb79985625c6981038caa00e6346408ed"><code>b9f9444</code></a>
update docs</li>
<li><a
href="https://github.com/actions/ai-inference/commit/419f171f16e478d9f608f4fe3fabe29f5dcdccae"><code>419f171</code></a>
Separate out MCP token</li>
<li><a
href="https://github.com/actions/ai-inference/commit/fc8527d1d95538cb45e8f49fbc95dc9e5681b849"><code>fc8527d</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/ai-inference/issues/74">#74</a>
from actions/dependabot/github_actions/actions-minor-e...</li>
<li><a
href="https://github.com/actions/ai-inference/commit/719349dfcccd7fbeee25901255dbf1a59c88d3fb"><code>719349d</code></a>
Merge branch 'main' into
dependabot/github_actions/actions-minor-e893b3f303</li>
<li><a
href="https://github.com/actions/ai-inference/commit/2762750922fa62f91dc6203df8b23c2684d5a52b"><code>2762750</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/ai-inference/issues/76">#76</a>
from actions/dependabot/npm_and_yarn/rollup/rollup-lin...</li>
<li><a
href="https://github.com/actions/ai-inference/commit/9386906af5e73f620356d314711097696823d770"><code>9386906</code></a>
chore(deps): bump <code>@​rollup/rollup-linux-x64-gnu</code> from 4.45.1
to 4.46.0</li>
<li><a
href="https://github.com/actions/ai-inference/commit/ca9eff70517ac00934ae11b2c2164fde6c48954e"><code>ca9eff7</code></a>
chore(deps): bump actions/publish-action in the actions-minor group</li>
<li><a
href="https://github.com/actions/ai-inference/commit/6bef1d0031bbc403a1e49d373ab67e03c2eb60ae"><code>6bef1d0</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/ai-inference/issues/72">#72</a>
from actions/mr/linters</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/ai-inference/compare/9693b137b6566bb66055a713613bf4f0493701eb...4b591cc5298ae9428fff72279eced918e444c409">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/ai-inference&package-manager=github_actions&previous-version=1.2.3&new-version=1.2.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-08-08 12:50:30 +01:00
dependabot[bot] b91bfac416 build(deps): bump docker/login-action from 3.4.0 to 3.5.0 (#4118)
Bumps [docker/login-action](https://github.com/docker/login-action) from
3.4.0 to 3.5.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/login-action/releases">docker/login-action's
releases</a>.</em></p>
<blockquote>
<h2>v3.5.0</h2>
<ul>
<li>Support dual-stack endpoints for AWS ECR by <a
href="https://github.com/Spacefish"><code>@​Spacefish</code></a> <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/874">docker/login-action#874</a>
<a
href="https://redirect.github.com/docker/login-action/pull/876">docker/login-action#876</a></li>
<li>Bump <code>@​aws-sdk/client-ecr</code> to 3.859.0 in <a
href="https://redirect.github.com/docker/login-action/pull/860">docker/login-action#860</a>
<a
href="https://redirect.github.com/docker/login-action/pull/878">docker/login-action#878</a></li>
<li>Bump <code>@​aws-sdk/client-ecr-public</code> to 3.859.0 in <a
href="https://redirect.github.com/docker/login-action/pull/860">docker/login-action#860</a>
<a
href="https://redirect.github.com/docker/login-action/pull/878">docker/login-action#878</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.57.0 to 0.62.1 in
<a
href="https://redirect.github.com/docker/login-action/pull/870">docker/login-action#870</a></li>
<li>Bump form-data from 2.5.1 to 2.5.5 in <a
href="https://redirect.github.com/docker/login-action/pull/875">docker/login-action#875</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v3.4.0...v3.5.0">https://github.com/docker/login-action/compare/v3.4.0...v3.5.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/login-action/commit/184bdaa0721073962dff0199f1fb9940f07167d1"><code>184bdaa</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/878">#878</a>
from docker/dependabot/npm_and_yarn/aws-sdk-dependenc...</li>
<li><a
href="https://github.com/docker/login-action/commit/5c6bc94683baa064818f51e7417087c2ac58b32c"><code>5c6bc94</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/caf405864315c6006c5581b540e5047cf728b4e7"><code>caf4058</code></a>
build(deps): bump the aws-sdk-dependencies group with 2 updates</li>
<li><a
href="https://github.com/docker/login-action/commit/ef38ec311a7df3f01475313e7c5bb584b74b112a"><code>ef38ec3</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/860">#860</a>
from docker/dependabot/npm_and_yarn/aws-sdk-dependenc...</li>
<li><a
href="https://github.com/docker/login-action/commit/d52e8ef81c0de894e9c95bed8de0ee5955ec7eb7"><code>d52e8ef</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/9644ab7025be3206ff4b12f1531a1b6919022b00"><code>9644ab7</code></a>
build(deps): bump the aws-sdk-dependencies group with 2 updates</li>
<li><a
href="https://github.com/docker/login-action/commit/7abd1d512621d8896b31f4ea992d207f15915ad6"><code>7abd1d5</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/875">#875</a>
from docker/dependabot/npm_and_yarn/form-data-2.5.5</li>
<li><a
href="https://github.com/docker/login-action/commit/1a81202c4fda440f3b33eca3381d5d39c7efe85e"><code>1a81202</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/876">#876</a>
from crazy-max/aws-public-dual-stack</li>
<li><a
href="https://github.com/docker/login-action/commit/d1ab30dc54161cbfd704562857677edf4dd7837a"><code>d1ab30d</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/f25ff28d1c8cd9a7c35896711238fed682755e1c"><code>f25ff28</code></a>
support dual-stack for aws public ecr</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/login-action/compare/74a5d142397b4f367a81961eba4e8cd7edddf772...184bdaa0721073962dff0199f1fb9940f07167d1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/login-action&package-manager=github_actions&previous-version=3.4.0&new-version=3.5.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-08-08 12:50:21 +01:00
Lukas 40936efe8d feature: import and export bookmarks to clipboard (#4093)
# Description of Changes

- add **import** and **export buttons** to bookmark editor (bottom
right) to **copy and past bookmark data**
- the export reads the hidden `<input id="bookmarkData">` field and uses
`navigator.clipboard.writeText()` to copy it to the clipboard
- the import reads from `navigator.clipboard.readText()` and sets the
internal `bookmarks` variable, which is used to update the UI elements
- after successful import or export, the buttons flash in green to give
visual feedback to the user
- this provides non-technical users with an intuitive method to copy
bookmarks between files
- I have seen, that this is also possible with the pipeline tool, but
this requires multiple steps and familiarity with the pipeline:
    1. use `extract-bookmarks` to generate `bookmarks.json`
    2. open the file and copy the data
    3. use `edit-table-of-contents` with the copied data
    4. process the target file
- challenges:
- I used `navigator.clipboard` as opposed to `document.execCommand`. The
latter is used in `account.html`, `errorBanner.html` and
`errorBanner.js`, but is
[deprecated](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand).
- I used the bootstrap-style rendering for the title attribute tooltip
for visual consistency in the bookmark editor, where the tooltip hovers
centered above the originating element. However, in most other places
the title tooltip follows the cursor and is slightly visually different.
- in case you are testing this on a mobile device (EDIT: or non-locally
hosted), the copy-to-clipboard might fail when hosted without SSL
(mobile only works in secure environment)
- similarly, when not using normal user interaction (i.e.
`element.click()` via console) the copy-to-clipboard will throw an error
`Clipboard write was blocked due to lack of user activation.`

---

## 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)
- [x] 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)

- [x] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)
<img width="600" alt="Bookmark editor with new Import/Export buttons in
the bottom right corner" title="Bookmark editor with new Import/Export
buttons in the bottom right corner"
src="https://github.com/user-attachments/assets/61b948a1-9f68-4793-9c86-a056bad6b7e1"
/>
<img width="300" alt="Bookmark editor with new Import/Export buttons
with low width layout" title="Bookmark editor with new Import/Export
buttons with low width layout"
src="https://github.com/user-attachments/assets/4fa7bc76-ca11-4268-b83a-8d1e612dc5b9"
/>


### 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.
2025-08-08 12:48:36 +01:00
Ludy b77d02e988 chore(templates): remove redundant fetch-utils.js script includes (#4092)
# Description of Changes

- **What was changed**: Removed all explicit `<script
th:src="@{'/js/fetch-utils.js'}"></script>` tags from various Thymeleaf
templates (`home.html`, `home-legacy.html`, `scanner-effect.html`,
etc.).
- **Why the change was made**: The `fetch-utils.js` script is already
included globally via `<th:block th:insert="~{fragments/common ::
head()}">` in `fragments/common.html` (line 156). Keeping redundant
includes leads to unnecessary script loading and potential duplication.

---

## 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-08-08 12:30:30 +01:00
stirlingbot[bot] d3c786d018 🌐 Sync Translations + Update README Progress Table (#4135)
### 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-08-08 12:21:29 +01:00
EthanHealy01 9861332040 Feature/v2/tooltips (#4112)
# Description of Changes

- added tooltips to ocr and compress
- added the tooltip component which can be used either directly, or
through the toolstep component

---

## 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-08-08 12:09:41 +01:00
EthanHealy01 f4e4831c0d Feature/v2/landing page (#4128)
# Description of Changes

- Added landing page styling
- Clicking add files now opens native file manager

---

## 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)
- [ ] 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-08-08 12:02:08 +01:00
Anthony StirlingandCopilot c4c9f3f303 Update CODEOWNERS (#4142)
# 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.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-08 11:38:57 +01:00
Ludy 6cd64a22ba build(local): simplify writeVersion task with WriteProperties plugin and enable build caching (#4139)
# Description of Changes

- **What was changed**:  
- Replaced the custom `writeVersion` task in `build.gradle` with the
built-in `WriteProperties` plugin configuration.
- Updated `gradle.properties` to enable `org.gradle.caching`
(uncommented) for local development.
- **Why the change was made**:  
- To reduce boilerplate and leverage Gradle’s native property-writing
capabilities for maintaining the version file.
- To improve build performance by reusing outputs via the Gradle build
cache.

- **Scope**:  
- These updates only affect local development and do not change
production or CI script

---

## 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-08-08 10:36:30 +01:00
LudyandCopilot aec5a8ddc5 feat(common): add configurable maxDPI limit for PDF-to-image conversion (#4129)
# Description of Changes

- **What was changed:**  
Added a new `maxDPI` property under `system` in `ApplicationProperties`;
updated `PdfUtils` to retrieve and enforce this configurable limit
instead of a hard-coded constant; modified `ConverterWebController` and
the PDF-to-image template to expose the limit to users; added
`pdfToImage.dpi` entries across all translation files; updated
`settings.yml.template` and `DeveloperGuide.md` to document the new
setting.
- **Why the change was made:**  
To allow deployments to tune the maximum DPI for PDF-to-image
conversions based on available resources, preventing excessive memory
usage and crashes caused by arbitrarily high DPI values.

Closes #3985

---

## 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.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-07 13:57:47 +01:00
Anthony Stirling 7dedf45b5d prune fix (#4133)
# 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-08-07 13:52:40 +01:00
Anthony Stirling 23aa43bd78 build fix (#4131)
into pr-4113

# 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-08-07 11:12:09 +01:00
Anthony Stirling 2b7671dc63 Update PR-Demo-Comment-with-react.yml 2025-08-05 19:55:53 +01:00
Anthony Stirling 2947abd2fd Update PR-Auto-Deploy-V2.yml 2025-08-05 19:55:35 +01:00
dependabot[bot] 63b64b5dc5 build(deps): bump io.swagger.core.v3:swagger-core-jakarta from 2.2.34 to 2.2.35 (#4117)
Bumps io.swagger.core.v3:swagger-core-jakarta from 2.2.34 to 2.2.35.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.swagger.core.v3:swagger-core-jakarta&package-manager=gradle&previous-version=2.2.34&new-version=2.2.35)](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-08-05 16:18:25 +01:00
dependabot[bot] 1d47f5e26a build(deps): bump docker/metadata-action from 5.7.0 to 5.8.0 (#4116)
Bumps
[docker/metadata-action](https://github.com/docker/metadata-action) from
5.7.0 to 5.8.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/metadata-action/releases">docker/metadata-action's
releases</a>.</em></p>
<blockquote>
<h2>v5.8.0</h2>
<ul>
<li>New <code>is_not_default_branch</code> global expression by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/metadata-action/pull/535">docker/metadata-action#535</a></li>
<li>Allow to match part of the git tag or value for semver/pep440 types
by <a href="https://github.com/crazy-max"><code>@​crazy-max</code></a>
in <a
href="https://redirect.github.com/docker/metadata-action/pull/536">docker/metadata-action#536</a>
<a
href="https://redirect.github.com/docker/metadata-action/pull/537">docker/metadata-action#537</a></li>
<li>Bump <code>@​actions/github</code> from 6.0.0 to 6.0.1 in <a
href="https://redirect.github.com/docker/metadata-action/pull/523">docker/metadata-action#523</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.56.0 to 0.62.1 in
<a
href="https://redirect.github.com/docker/metadata-action/pull/526">docker/metadata-action#526</a></li>
<li>Bump form-data from 2.5.1 to 2.5.5 in <a
href="https://redirect.github.com/docker/metadata-action/pull/533">docker/metadata-action#533</a></li>
<li>Bump moment-timezone from 0.5.47 to 0.6.0 in <a
href="https://redirect.github.com/docker/metadata-action/pull/525">docker/metadata-action#525</a></li>
<li>Bump semver from 7.7.1 to 7.7.2 in <a
href="https://redirect.github.com/docker/metadata-action/pull/524">docker/metadata-action#524</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/metadata-action/compare/v5.7.0...v5.8.0">https://github.com/docker/metadata-action/compare/v5.7.0...v5.8.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/metadata-action/commit/c1e51972afc2121e065aed6d45c65596fe445f3f"><code>c1e5197</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/537">#537</a>
from crazy-max/pep440-match</li>
<li><a
href="https://github.com/docker/metadata-action/commit/89dd65a56942f24df76cdf7a4c23b89e9e0c64f9"><code>89dd65a</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/metadata-action/commit/699ee45cf1d1e4c00bb9ca9bacb9f983fc58fbf6"><code>699ee45</code></a>
allow to match part of the git tag or value for pep440 type</li>
<li><a
href="https://github.com/docker/metadata-action/commit/e0542a6360c9f152bbf3353bd9c94564a730f25f"><code>e0542a6</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/536">#536</a>
from crazy-max/semver-match</li>
<li><a
href="https://github.com/docker/metadata-action/commit/b7facdfcef4956d2d16250632ca1d9fb16ed637c"><code>b7facdf</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/metadata-action/commit/81c60dfb8b905e3a16457c3da8880fc62e9051b8"><code>81c60df</code></a>
allow to match part of the git tag or value for semver type</li>
<li><a
href="https://github.com/docker/metadata-action/commit/de1119515dcfb4b110f21f67dc739b3ba1472a84"><code>de11195</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/535">#535</a>
from crazy-max/not_def_branch</li>
<li><a
href="https://github.com/docker/metadata-action/commit/2f9c64b1b1b1f3dd8b9e5a74ae4db13087cb814e"><code>2f9c64b</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/metadata-action/issues/533">#533</a>
from docker/dependabot/npm_and_yarn/form-data-2.5.5</li>
<li><a
href="https://github.com/docker/metadata-action/commit/510f74697528050f83e777f81df8cfccb153ccd3"><code>510f746</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/metadata-action/commit/2bc3f4e0f13f667fe2ccb93725d524c114c6ce80"><code>2bc3f4e</code></a>
is_not_default_branch global expression</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/metadata-action/compare/902fa8ec7d6ecbf8d84d538b9b233a880e428804...c1e51972afc2121e065aed6d45c65596fe445f3f">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/metadata-action&package-manager=github_actions&previous-version=5.7.0&new-version=5.8.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-08-05 16:17:59 +01:00
dependabot[bot] 2a20ffd09a build(deps): bump commonmarkVersion from 0.25.0 to 0.25.1 (#4115)
Bumps `commonmarkVersion` from 0.25.0 to 0.25.1.
Updates `org.commonmark:commonmark` from 0.25.0 to 0.25.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/commonmark/commonmark-java/releases">org.commonmark:commonmark's
releases</a>.</em></p>
<blockquote>
<h2>commonmark-java 0.25.1</h2>
<h3>Fixed</h3>
<ul>
<li>footnotes: Fix parsing of footnote definitions containing multiple
paragraphs
separated by blank lines. Before it only worked if paragraphs were
separated
by lines of 4 spaces. (<a
href="https://redirect.github.com/commonmark/commonmark-java/issues/388">#388</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md">org.commonmark:commonmark's
changelog</a>.</em></p>
<blockquote>
<h2>[0.25.1] - 2025-08-01</h2>
<h3>Fixed</h3>
<ul>
<li>footnotes: Fix parsing of footnote definitions containing multiple
paragraphs
separated by blank lines. Before it only worked if paragraphs were
separated
by lines of 4 spaces. (<a
href="https://redirect.github.com/commonmark/commonmark-java/issues/388">#388</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/b703d6a3561a6eb00f6de8dfab85cf0301cb7a2d"><code>b703d6a</code></a>
[maven-release-plugin] prepare release commonmark-parent-0.25.1</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/eb93eca29e8ea0369186b6a22afd2b1f853df1f8"><code>eb93eca</code></a>
Merge pull request <a
href="https://redirect.github.com/commonmark/commonmark-java/issues/390">#390</a>
from commonmark/release-0.25.1</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/bd50c7d587c435c80eeb1f59d2c41fed3efefb33"><code>bd50c7d</code></a>
Prepare CHANGELOG for version 0.25.1</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/226e8e970f4525466b28ff4a37b131776204e8b8"><code>226e8e9</code></a>
Merge pull request <a
href="https://redirect.github.com/commonmark/commonmark-java/issues/389">#389</a>
from commonmark/issue-388-footnotes-multiple-paragraphs</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/3111fed6c73dc6e961c590c58973b44e12fe5464"><code>3111fed</code></a>
footnotes: Fix multiple paragraphs separated by blank lines</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/33737b7d5dd65153f2d022f7b5770f0fa0630296"><code>33737b7</code></a>
CHANGELOG: Add issue links</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/c577edfe08e523ab806a111f629a518c4de660be"><code>c577edf</code></a>
README: Bump version</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/8d5918d4efd3ad1dca4626af8a516872f7aa6121"><code>8d5918d</code></a>
[maven-release-plugin] prepare for next development iteration</li>
<li>See full diff in <a
href="https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.0...commonmark-parent-0.25.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `org.commonmark:commonmark-ext-gfm-tables` from 0.25.0 to 0.25.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/commonmark/commonmark-java/releases">org.commonmark:commonmark-ext-gfm-tables's
releases</a>.</em></p>
<blockquote>
<h2>commonmark-java 0.25.1</h2>
<h3>Fixed</h3>
<ul>
<li>footnotes: Fix parsing of footnote definitions containing multiple
paragraphs
separated by blank lines. Before it only worked if paragraphs were
separated
by lines of 4 spaces. (<a
href="https://redirect.github.com/commonmark/commonmark-java/issues/388">#388</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md">org.commonmark:commonmark-ext-gfm-tables's
changelog</a>.</em></p>
<blockquote>
<h2>[0.25.1] - 2025-08-01</h2>
<h3>Fixed</h3>
<ul>
<li>footnotes: Fix parsing of footnote definitions containing multiple
paragraphs
separated by blank lines. Before it only worked if paragraphs were
separated
by lines of 4 spaces. (<a
href="https://redirect.github.com/commonmark/commonmark-java/issues/388">#388</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/b703d6a3561a6eb00f6de8dfab85cf0301cb7a2d"><code>b703d6a</code></a>
[maven-release-plugin] prepare release commonmark-parent-0.25.1</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/eb93eca29e8ea0369186b6a22afd2b1f853df1f8"><code>eb93eca</code></a>
Merge pull request <a
href="https://redirect.github.com/commonmark/commonmark-java/issues/390">#390</a>
from commonmark/release-0.25.1</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/bd50c7d587c435c80eeb1f59d2c41fed3efefb33"><code>bd50c7d</code></a>
Prepare CHANGELOG for version 0.25.1</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/226e8e970f4525466b28ff4a37b131776204e8b8"><code>226e8e9</code></a>
Merge pull request <a
href="https://redirect.github.com/commonmark/commonmark-java/issues/389">#389</a>
from commonmark/issue-388-footnotes-multiple-paragraphs</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/3111fed6c73dc6e961c590c58973b44e12fe5464"><code>3111fed</code></a>
footnotes: Fix multiple paragraphs separated by blank lines</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/33737b7d5dd65153f2d022f7b5770f0fa0630296"><code>33737b7</code></a>
CHANGELOG: Add issue links</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/c577edfe08e523ab806a111f629a518c4de660be"><code>c577edf</code></a>
README: Bump version</li>
<li><a
href="https://github.com/commonmark/commonmark-java/commit/8d5918d4efd3ad1dca4626af8a516872f7aa6121"><code>8d5918d</code></a>
[maven-release-plugin] prepare for next development iteration</li>
<li>See full diff in <a
href="https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.0...commonmark-parent-0.25.1">compare
view</a></li>
</ul>
</details>
<br />


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-08-05 16:16:32 +01:00
dependabot[bot] 47a49c5353 build(deps): bump org.eclipse.angus:angus-mail from 2.0.3 to 2.0.4 (#4114)
Bumps
[org.eclipse.angus:angus-mail](https://github.com/eclipse-ee4j/angus-mail)
from 2.0.3 to 2.0.4.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/eclipse-ee4j/angus-mail/commit/37c1c6ca1e7bdc1c7be6ed47eb6f1e388d3cbc79"><code>37c1c6c</code></a>
Prepare release org.eclipse.angus:all:2.0.4</li>
<li><a
href="https://github.com/eclipse-ee4j/angus-mail/commit/a53d904ca190c7241ee923b14bae6e16d0726c35"><code>a53d904</code></a>
Update changes log (<a
href="https://redirect.github.com/eclipse-ee4j/angus-mail/issues/169">#169</a>)</li>
<li><a
href="https://github.com/eclipse-ee4j/angus-mail/commit/5d0e7b3f5155130c55e6375ae3f5dc5d0a3564ab"><code>5d0e7b3</code></a>
Update changes log</li>
<li><a
href="https://github.com/eclipse-ee4j/angus-mail/commit/84fe702f7a4837f325b878f17fee148a7e8ba951"><code>84fe702</code></a>
Fix issue299 (<a
href="https://redirect.github.com/eclipse-ee4j/angus-mail/issues/166">#166</a>)
(<a
href="https://redirect.github.com/eclipse-ee4j/angus-mail/issues/167">#167</a>)</li>
<li><a
href="https://github.com/eclipse-ee4j/angus-mail/commit/a15041d503cefb380e3a5284eb7144eb92d82e93"><code>a15041d</code></a>
Update README.md</li>
<li><a
href="https://github.com/eclipse-ee4j/angus-mail/commit/005bec40252bc732d2822cb9ab79902a4adc6f17"><code>005bec4</code></a>
Merge pull request <a
href="https://redirect.github.com/eclipse-ee4j/angus-mail/issues/138">#138</a>
from eclipse-ee4j/2.0.3-RELEASE</li>
<li><a
href="https://github.com/eclipse-ee4j/angus-mail/commit/637b1913d26a0420a6b6bf1ed7ed0ebb868d8a57"><code>637b191</code></a>
Update TCK-Results.md</li>
<li><a
href="https://github.com/eclipse-ee4j/angus-mail/commit/2a375178f614071e872cd518d18ffa337af19029"><code>2a37517</code></a>
Prepare next development cycle for 2.0.4-SNAPSHOT</li>
<li>See full diff in <a
href="https://github.com/eclipse-ee4j/angus-mail/compare/2.0.3...2.0.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.eclipse.angus:angus-mail&package-manager=gradle&previous-version=2.0.3&new-version=2.0.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-08-05 16:16:10 +01:00
Peter Dave Hello 17ef9720e5 Add new keys to zh_TW Traditional Chinese ignore translation list (#4108)
# Description of Changes

Summary from GitHub Copilot:

> This pull request updates the `scripts/ignore_translation.toml` file
to add new translation keys to the ignore list for the `zh_TW` locale.
> 
> *
[`scripts/ignore_translation.toml`](diffhunk://#diff-607e01559fa08179f8efe9ee6f73dbbedaac20cdd2e6d50968253445d1cd00c7R1030-R1031):
Added `poweredBy` and `showJS.tags` to the `ignore` list for the `zh_TW`
locale.

---

## 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)
- [x] 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-08-04 16:25:38 +01:00
ConnorYohandConnor Yoh 90f0c5826a Added structure for filemanager (#4078)
Overview

Replaced scattered file inputs with a unified modal-based upload system.
Users now upload files via a global Files button with intelligent
tool-aware filtering.

  Key Changes

  🔄 New Upload Flow

  - Before: Direct file inputs throughout the UI
- After: Single Files button → Modal → Tool filters files automatically

  🎯 Smart File Filtering

  - Modal shows only supported file types based on selected tool
  - Visual indicators for unsupported files (grayed out + badges)
  - Automatic duplicate detection

   Enhanced UX

  - Files button shows active state when modal is open
  - Consistent upload experience across all tools
  - Professional modal workflow

  Architecture

  New Components

  FilesModalProvider → FileUploadModal → Tool-aware filtering

  Button System Redesign

  type: 'navigation' | 'modal' | 'action'
  // Only navigation buttons stay active
  // Modal buttons show active when modal open

  Files Changed

  -  QuickAccessBar.tsx - Added Files button
  -  FileUploadModal.tsx - New tool-aware modal
  -  HomePage.tsx - Integrated modal system
  -  ConvertE2E.spec.ts - Updated tests for modal workflow

  Benefits

  - Unified UX: One place to upload files
  - Smart Filtering: Only see relevant file types
  - Better Architecture: Clean separation of concerns
  - Improved Testing: Reliable test automation

Migration: File uploads now go through Files button → modal instead of
direct inputs. All existing functionality preserved.

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-04 15:01:36 +01:00
stirlingbot[bot] 6634b5d6e4 🤖 format everything with pre-commit by stirlingbot (#4104)
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-08-02 23:19:52 +01:00
Balázs Szücs ae8f68427b Updated Hungarian translation (#4094)
# Description of Changes

Should be last untranslated string.

<!--
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)
- [x] 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.
2025-08-02 23:19:24 +01:00
stirlingbot[bot] c40fac8053 Update 3rd Party Licenses (#4073)
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-08-02 23:19:08 +01:00
stirlingbot[bot] 56c79eb63c 🤖 format everything with pre-commit by stirlingbot (#4075)
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-08-02 23:18:48 +01:00
LudyandCopilot 62779d99d1 fix(stamp): validate image filename only for image stamp type (#4099)
# Description of Changes

- **What was changed**: Moved the filename validation logic for
`stampImage` inside a condition that checks whether the stamp type is
`"image"`.
- **Why the change was made**: Previously, the validation was applied
regardless of stamp type, leading to unnecessary errors for
non-image-based stamps where no `stampImage` is provided.

Closes #4097

---

## 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.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-02 23:16:15 +01:00
Ludy 9e0f6dd2e1 style(spotless): centralize and expand formatting config in root build.gradle (#4098)
# Description of Changes

- Removed redundant `spotless` configurations from `app/common`,
`app/core`, and `app/proprietary` modules.
- Consolidated all formatting logic into the root `build.gradle` file.
- Extended Spotless support to include:
  - YAML files (`*.yml`, `*.yaml`)
  - Gradle scripts (`*.gradle`, including nested `app/**/*.gradle`)
- Updated `googleJavaFormatVersion` from `1.27.0` to `1.28.0`.

This change improves maintainability by enforcing consistent formatting
across all modules from a single location. Additionally, it ensures
formatting is applied to Gradle and YAML files, which were previously
unformatted.

---

## 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-08-02 23:14:03 +01:00
Ludy 77a27930b5 ci(github-actions): improve concurrency grouping with PR number fallback (#4101)
# Description of Changes

- Updated the `concurrency.group` key in the following GitHub Actions
workflows:
  - `.github/workflows/build.yml`
  - `.github/workflows/check_properties.yml`
  - `.github/workflows/sonarqube.yml`
- The grouping string now uses `github.event.pull_request.number` (if
present) as a fallback before falling back to `ref_name` or `ref`.
- This helps ensure better grouping for PR-based workflows, improving
job cancellation behavior and avoiding unnecessary parallel job
execution when multiple pushes occur on the same PR.

No functional behavior is changed in the actual build or check logic.

---

## 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-08-02 23:12:51 +01:00
Peter Dave Hello 5f1f492888 Update zh_TW Traditional Chinese translation (#4100)
# Description of Changes

Summary by GitHub Copilot:

> 
> This pull request updates translations in the
`messages_zh_TW.properties` file to improve localization for Traditional
Chinese. The changes focus on replacing English text with accurate
Chinese translations.
> 
> ### Localization Updates:
> 
> * Updated `invalidRoleMessage` to translate "Invalid role" into
Traditional Chinese as "無效的角色" for better localization.
> * Updated `proFeatures` to translate "Pro Features" into Traditional
Chinese as "專業版功能" to align with the rest of the localized content.
> 

---

## 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)
- [x] 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-08-02 23:12:32 +01:00
ConnorYohandConnor Yoh 24a9104ebf Fix GB language file (#4087)
Language file fix

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-08-01 20:14:05 +01:00
stirlingbot[bot] 03f86fc59c Update Frontend 3rd Party Licenses (#4074)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2025-08-01 17:22:30 +01:00
Ludy a5d219ed05 chore(pre-commit): enable test source formatting and build validation on push to main (#4067)
# Description of Changes

This PR improves the pre-commit workflow and formatting configuration to
enforce consistency and catch issues earlier in CI:

- **Pre-commit workflow (`pre_commit.yml`)**:
- Trigger now runs on `push` to `main` (previously scheduled weekly
only).
- Adds a `gradlew clean build` step to ensure the codebase compiles as
part of the pre-commit validation.
- Configures Java 17 using the Temurin distribution via
`actions/setup-java`.

- **.pre-commit-config.yaml**:
  - Updated `ruff` to version `v0.12.7` (from `v0.12.0`).
  - Updated `gitleaks` to `v8.28.0` (from `v8.27.2`).

- **Spotless configuration**:
- Added formatting for `test` sources across all Gradle modules
(`common`, `core`, `proprietary`, `stirling-pdf`).
- Ensures that test code follows the same formatting rules as production
code.

These changes help improve early feedback in development and CI by
integrating linting, formatting, and build checks directly into the
workflow on code pushes.


---

## 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)
- [ ] 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-08-01 17:21:28 +01:00
9c9acbfb5b V2: Convert Tool (#3828)
🔄 Dynamic Processing Strategies

- Adaptive routing: Same tool uses different backend endpoints based on
file analysis
- Combined vs separate processing: Intelligently chooses between merge
operations and individual file processing
- Cross-format workflows: Enable complex conversions like "mixed files →
PDF" that other tools can't handle

  ⚙️ Format-Specific Intelligence

  Each conversion type gets tailored options:
  - HTML/ZIP → PDF: Zoom controls (0.1-3.0 increments) with live preview
  - Email → PDF: Attachment handling, size limits, recipient control
  - PDF → PDF/A: Digital signature detection with warnings
  - Images → PDF: Smart combining vs individual file options

 File Architecture

  Core Implementation:
  ├── Convert.tsx                     # Main stepped workflow UI
├── ConvertSettings.tsx # Centralized settings with smart detection
├── GroupedFormatDropdown.tsx # Enhanced format selector with grouping
├── useConvertParameters.ts # Smart detection & parameter management
  ├── useConvertOperation.ts         # Multi-strategy processing logic
  └── Settings Components:
      ├── ConvertFromWebSettings.tsx      # HTML zoom controls
      ├── ConvertFromEmailSettings.tsx    # Email attachment options
├── ConvertToPdfaSettings.tsx # PDF/A with signature detection
      ├── ConvertFromImageSettings.tsx    # Image PDF options
      └── ConvertToImageSettings.tsx      # PDF to image options

 Utility Layer

  Utils & Services:
├── convertUtils.ts # Format detection & endpoint routing
  ├── fileResponseUtils.ts          # Generic API response handling
└── setupTests.ts # Enhanced test environment with crypto mocks

  Testing & Quality

  Comprehensive Test Coverage

  Test Suite:
├── useConvertParameters.test.ts # Parameter logic & smart detection
  ├── useConvertParametersAutoDetection.test.ts  # File type analysis
├── ConvertIntegration.test.tsx # End-to-end conversion workflows
  ├── ConvertSmartDetectionIntegration.test.tsx  # Mixed file scenarios
  ├── ConvertE2E.spec.ts                     # Playwright browser tests
├── convertUtils.test.ts # Utility function validation
  └── fileResponseUtils.test.ts              # API response handling

  Advanced Test Features

  - Crypto API mocking: Proper test environment for file hashing
  - File.arrayBuffer() polyfills: Complete browser API simulation
  - Multi-file scenario testing: Complex batch processing validation
- CI/CD integration: Vitest runs in GitHub Actions with proper artifacts

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-08-01 16:08:04 +01:00
dependabot[bot] 31598f3f1e build(deps): bump org.springframework.boot:spring-boot-dependencies from 3.5.3 to 3.5.4 (#4058)
[//]: # (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
[org.springframework.boot:spring-boot-dependencies](https://github.com/spring-projects/spring-boot)
from 3.5.3 to 3.5.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/spring-projects/spring-boot/releases">org.springframework.boot:spring-boot-dependencies's
releases</a>.</em></p>
<blockquote>
<h2>v3.5.4</h2>
<h2>🐞 Bug Fixes</h2>
<ul>
<li>LambdaSafe.withFilter is not public <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46474">#46474</a></li>
<li>Executable JAR application class encounters performance issues when
used with Palo Alto Network Cortex XDR agent <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46402">#46402</a></li>
<li>Runtime dependencies are missing from aotCompileClasspath and
aotTestCompileClasspath when using Kotlin <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46398">#46398</a></li>
<li>Additional fields for structured JSON logging incompatible with
nested ecs logging in 3.5.x <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46351">#46351</a></li>
<li>Change in DefaultErrorAttributes alters the shape of API validation
error responses <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46260">#46260</a></li>
<li>jdbc.connections.active and jdbc.connections.idle metrics are not
available when using Hikari in a native image <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46225">#46225</a></li>
<li>developmentOnly and testAndDevelopmentOnly dependencies may prevent
implementation dependencies from being included in the uber-jar <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46205">#46205</a></li>
<li>Hash calculation for uber archive entries that require unpacking is
inefficient <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46203">#46203</a></li>
<li>Permissions are applied inconsistently when building uber archives
with Gradle <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46194">#46194</a></li>
<li>Environment variables using legacy dash format can no longer be
bound <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46184">#46184</a></li>
<li>EmbeddedWebServerFactoryCustomizerAutoConfiguration fails when
undertow-core is on the classpath and undertow-servlet is not <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46180">#46180</a></li>
<li>Executable JAR application class encounters performance issues <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46177">#46177</a></li>
<li>Executable JAR application class encounters performance issues <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46176">#46176</a></li>
<li>Setting spring.reactor.context-propagation has no effect when lazy
initialization is enabled <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46174">#46174</a></li>
<li>Setting spring.netty.leak-detection has no effect when lazy
initialization is enabled <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46170">#46170</a></li>
<li>SslInfo does not use its Clock when checking certificate validity <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46011">#46011</a></li>
</ul>
<h2>📔 Documentation</h2>
<ul>
<li>Fix description of spring.batch.job.enabled <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46247">#46247</a></li>
<li>Fix broken Kotlin examples in reference documentation <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46168">#46168</a></li>
<li>Add Logback Access Reactor Netty to community starters <a
href="https://redirect.github.com/spring-projects/spring-boot/pull/46060">#46060</a></li>
</ul>
<h2>🔨 Dependency Upgrades</h2>
<ul>
<li>Upgrade to ActiveMQ 6.1.7 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46373">#46373</a></li>
<li>Upgrade to Caffeine 3.2.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46432">#46432</a></li>
<li>Upgrade to Couchbase Client 3.8.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46460">#46460</a></li>
<li>Upgrade to GraphQL Java 24.1 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46395">#46395</a></li>
<li>Upgrade to Groovy 4.0.28 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46516">#46516</a></li>
<li>Upgrade to Hibernate 6.6.22.Final <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46492">#46492</a></li>
<li>Upgrade to HikariCP 6.3.1 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46493">#46493</a></li>
<li>Upgrade to Infinispan 15.2.5.Final <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46461">#46461</a></li>
<li>Upgrade to Jackson Bom 2.19.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46494">#46494</a></li>
<li>Upgrade to Jetty 12.0.23 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46375">#46375</a></li>
<li>Upgrade to MariaDB 3.5.4 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46376">#46376</a></li>
<li>Upgrade to Maven Invoker Plugin 3.9.1 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46377">#46377</a></li>
<li>Upgrade to Micrometer 1.15.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46280">#46280</a></li>
<li>Upgrade to Micrometer Tracing 1.5.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46281">#46281</a></li>
<li>Upgrade to MSSQL JDBC 12.10.1.jre11 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46378">#46378</a></li>
<li>Upgrade to MySQL 9.3.0 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46371">#46371</a></li>
<li>Upgrade to Neo4j Java Driver 5.28.9 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46434">#46434</a></li>
<li>Upgrade to Netty 4.1.123.Final <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46435">#46435</a></li>
<li>Upgrade to Prometheus Client 1.3.10 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46379">#46379</a></li>
<li>Upgrade to Reactor Bom 2024.0.8 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46282">#46282</a></li>
<li>Upgrade to RxJava3 3.1.11 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46380">#46380</a></li>
<li>Upgrade to Spring AMQP 3.2.6 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46283">#46283</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/925f9bc6ba99f0eaffce1e357282d3672b88e2a5"><code>925f9bc</code></a>
Release v3.5.4</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/d82fb358acc9e99af28303ccd922df634e1d69ee"><code>d82fb35</code></a>
Merge branch '3.4.x' into 3.5.x</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/4b6064f4aaf8f00633d29f3777e531f2f0aebd0e"><code>4b6064f</code></a>
Next development version (v3.4.9-SNAPSHOT)</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/a39c8f034a2ba187b4ddb703666531b8689cadcc"><code>a39c8f0</code></a>
Merge branch '3.4.x' into 3.5.x</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/99d53dec18924d5b07f528b00a37ced110602341"><code>99d53de</code></a>
Upgrade to Spring Integration 6.5.1</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/1b4aad592e62335ef3c290414bc6bf4f8daf2a2b"><code>1b4aad5</code></a>
Upgrade to Groovy 4.0.28</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/3f0f79b982b6847893ecf086875461223288bb0e"><code>3f0f79b</code></a>
Upgrade to Spring Integration 6.4.6</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/ff8443c016ec1c7fe140c6ce6a58978af05025a8"><code>ff8443c</code></a>
Upgrade to Groovy 4.0.28</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/aed85504210a7c79fbc49831f2fb09f77661bce6"><code>aed8550</code></a>
Merge branch '3.4.x' into 3.5.x</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/5406976ee99187d2b6d69d5759f75a72ae757c82"><code>5406976</code></a>
Apply commercial input consistently</li>
<li>Additional commits viewable in <a
href="https://github.com/spring-projects/spring-boot/compare/v3.5.3...v3.5.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.springframework.boot:spring-boot-dependencies&package-manager=gradle&previous-version=3.5.3&new-version=3.5.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-08-01 14:26:25 +01:00
dependabot[bot] 6aa474596e build(deps): bump org.springframework.boot from 3.5.3 to 3.5.4 (#4059)
[//]: # (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
[org.springframework.boot](https://github.com/spring-projects/spring-boot)
from 3.5.3 to 3.5.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/spring-projects/spring-boot/releases">org.springframework.boot's
releases</a>.</em></p>
<blockquote>
<h2>v3.5.4</h2>
<h2>🐞 Bug Fixes</h2>
<ul>
<li>LambdaSafe.withFilter is not public <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46474">#46474</a></li>
<li>Executable JAR application class encounters performance issues when
used with Palo Alto Network Cortex XDR agent <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46402">#46402</a></li>
<li>Runtime dependencies are missing from aotCompileClasspath and
aotTestCompileClasspath when using Kotlin <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46398">#46398</a></li>
<li>Additional fields for structured JSON logging incompatible with
nested ecs logging in 3.5.x <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46351">#46351</a></li>
<li>Change in DefaultErrorAttributes alters the shape of API validation
error responses <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46260">#46260</a></li>
<li>jdbc.connections.active and jdbc.connections.idle metrics are not
available when using Hikari in a native image <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46225">#46225</a></li>
<li>developmentOnly and testAndDevelopmentOnly dependencies may prevent
implementation dependencies from being included in the uber-jar <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46205">#46205</a></li>
<li>Hash calculation for uber archive entries that require unpacking is
inefficient <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46203">#46203</a></li>
<li>Permissions are applied inconsistently when building uber archives
with Gradle <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46194">#46194</a></li>
<li>Environment variables using legacy dash format can no longer be
bound <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46184">#46184</a></li>
<li>EmbeddedWebServerFactoryCustomizerAutoConfiguration fails when
undertow-core is on the classpath and undertow-servlet is not <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46180">#46180</a></li>
<li>Executable JAR application class encounters performance issues <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46177">#46177</a></li>
<li>Executable JAR application class encounters performance issues <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46176">#46176</a></li>
<li>Setting spring.reactor.context-propagation has no effect when lazy
initialization is enabled <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46174">#46174</a></li>
<li>Setting spring.netty.leak-detection has no effect when lazy
initialization is enabled <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46170">#46170</a></li>
<li>SslInfo does not use its Clock when checking certificate validity <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46011">#46011</a></li>
</ul>
<h2>📔 Documentation</h2>
<ul>
<li>Fix description of spring.batch.job.enabled <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46247">#46247</a></li>
<li>Fix broken Kotlin examples in reference documentation <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46168">#46168</a></li>
<li>Add Logback Access Reactor Netty to community starters <a
href="https://redirect.github.com/spring-projects/spring-boot/pull/46060">#46060</a></li>
</ul>
<h2>🔨 Dependency Upgrades</h2>
<ul>
<li>Upgrade to ActiveMQ 6.1.7 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46373">#46373</a></li>
<li>Upgrade to Caffeine 3.2.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46432">#46432</a></li>
<li>Upgrade to Couchbase Client 3.8.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46460">#46460</a></li>
<li>Upgrade to GraphQL Java 24.1 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46395">#46395</a></li>
<li>Upgrade to Groovy 4.0.28 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46516">#46516</a></li>
<li>Upgrade to Hibernate 6.6.22.Final <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46492">#46492</a></li>
<li>Upgrade to HikariCP 6.3.1 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46493">#46493</a></li>
<li>Upgrade to Infinispan 15.2.5.Final <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46461">#46461</a></li>
<li>Upgrade to Jackson Bom 2.19.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46494">#46494</a></li>
<li>Upgrade to Jetty 12.0.23 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46375">#46375</a></li>
<li>Upgrade to MariaDB 3.5.4 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46376">#46376</a></li>
<li>Upgrade to Maven Invoker Plugin 3.9.1 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46377">#46377</a></li>
<li>Upgrade to Micrometer 1.15.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46280">#46280</a></li>
<li>Upgrade to Micrometer Tracing 1.5.2 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46281">#46281</a></li>
<li>Upgrade to MSSQL JDBC 12.10.1.jre11 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46378">#46378</a></li>
<li>Upgrade to MySQL 9.3.0 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46371">#46371</a></li>
<li>Upgrade to Neo4j Java Driver 5.28.9 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46434">#46434</a></li>
<li>Upgrade to Netty 4.1.123.Final <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46435">#46435</a></li>
<li>Upgrade to Prometheus Client 1.3.10 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46379">#46379</a></li>
<li>Upgrade to Reactor Bom 2024.0.8 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46282">#46282</a></li>
<li>Upgrade to RxJava3 3.1.11 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46380">#46380</a></li>
<li>Upgrade to Spring AMQP 3.2.6 <a
href="https://redirect.github.com/spring-projects/spring-boot/issues/46283">#46283</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/925f9bc6ba99f0eaffce1e357282d3672b88e2a5"><code>925f9bc</code></a>
Release v3.5.4</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/d82fb358acc9e99af28303ccd922df634e1d69ee"><code>d82fb35</code></a>
Merge branch '3.4.x' into 3.5.x</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/4b6064f4aaf8f00633d29f3777e531f2f0aebd0e"><code>4b6064f</code></a>
Next development version (v3.4.9-SNAPSHOT)</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/a39c8f034a2ba187b4ddb703666531b8689cadcc"><code>a39c8f0</code></a>
Merge branch '3.4.x' into 3.5.x</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/99d53dec18924d5b07f528b00a37ced110602341"><code>99d53de</code></a>
Upgrade to Spring Integration 6.5.1</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/1b4aad592e62335ef3c290414bc6bf4f8daf2a2b"><code>1b4aad5</code></a>
Upgrade to Groovy 4.0.28</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/3f0f79b982b6847893ecf086875461223288bb0e"><code>3f0f79b</code></a>
Upgrade to Spring Integration 6.4.6</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/ff8443c016ec1c7fe140c6ce6a58978af05025a8"><code>ff8443c</code></a>
Upgrade to Groovy 4.0.28</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/aed85504210a7c79fbc49831f2fb09f77661bce6"><code>aed8550</code></a>
Merge branch '3.4.x' into 3.5.x</li>
<li><a
href="https://github.com/spring-projects/spring-boot/commit/5406976ee99187d2b6d69d5759f75a72ae757c82"><code>5406976</code></a>
Apply commercial input consistently</li>
<li>Additional commits viewable in <a
href="https://github.com/spring-projects/spring-boot/compare/v3.5.3...v3.5.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.springframework.boot&package-manager=gradle&previous-version=3.5.3&new-version=3.5.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-08-01 14:26:03 +01:00
dependabot[bot] 213949d499 build(deps): bump com.opencsv:opencsv from 5.11.2 to 5.12.0 (#4060)
Bumps com.opencsv:opencsv from 5.11.2 to 5.12.0.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.opencsv:opencsv&package-manager=gradle&previous-version=5.11.2&new-version=5.12.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-08-01 14:25:48 +01:00
dependabot[bot] 1399a306a6 build(deps): bump edu.sc.seis.launch4j from 3.0.6 to 3.0.7 (#4062)
Bumps edu.sc.seis.launch4j from 3.0.6 to 3.0.7.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=edu.sc.seis.launch4j&package-manager=gradle&previous-version=3.0.6&new-version=3.0.7)](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-08-01 14:25:36 +01:00
dependabot[bot]andAnthony Stirling 1eb96f08df build(deps): bump github/codeql-action from 3.29.3 to 3.29.5 (#4061)
Bumps [github/codeql-action](https://github.com/github/codeql-action)
from 3.29.3 to 3.29.5.
<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.29.5</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.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>
<p>See the full <a
href="https://github.com/github/codeql-action/blob/v3.29.5/CHANGELOG.md">CHANGELOG.md</a>
for more information.</p>
<h2>v3.29.4</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.29.4 - 23 Jul 2025</h2>
<p>No user facing changes.</p>
<p>See the full <a
href="https://github.com/github/codeql-action/blob/v3.29.4/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.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>
<ul>
<li>Experimental: When the <code>quality-queries</code> input for the
<code>init</code> action is provided with an argument, separate
<code>.quality.sarif</code> files are produced and uploaded for each
language with the results of the specified queries. Do not use this in
production as it is part of an internal experiment and subject to change
at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/2935">#2935</a></li>
</ul>
<h2>3.29.1 - 27 Jun 2025</h2>
<ul>
<li>Fix bug in PR analysis where user-provided <code>include</code>
query filter fails to exclude non-included queries. <a
href="https://redirect.github.com/github/codeql-action/pull/2938">#2938</a></li>
<li>Update default CodeQL bundle version to 2.22.1. <a
href="https://redirect.github.com/github/codeql-action/pull/2950">#2950</a></li>
</ul>
<h2>3.29.0 - 11 Jun 2025</h2>
<ul>
<li>Update default CodeQL bundle version to 2.22.0. <a
href="https://redirect.github.com/github/codeql-action/pull/2925">#2925</a></li>
<li>Bump minimum CodeQL bundle version to 2.16.6. <a
href="https://redirect.github.com/github/codeql-action/pull/2912">#2912</a></li>
</ul>
<h2>3.28.21 - 28 July 2025</h2>
<p>No user facing changes.</p>
<h2>3.28.20 - 21 July 2025</h2>
<ul>
<li>Remove support for combining SARIF files from a single upload for
GHES 3.18, see <a
href="https://github.blog/changelog/2024-05-06-code-scanning-will-stop-combining-runs-from-a-single-upload/">the
changelog post</a>. <a
href="https://redirect.github.com/github/codeql-action/pull/2959">#2959</a></li>
</ul>
<h2>3.28.19 - 03 Jun 2025</h2>
<ul>
<li>The CodeQL Action no longer includes its own copy of the extractor
for the <code>actions</code> language, which is currently in public
preview.
The <code>actions</code> extractor has been included in the CodeQL CLI
since v2.20.6. If your workflow has enabled the <code>actions</code>
language <em>and</em> you have pinned
your <code>tools:</code> property to a specific version of the CodeQL
CLI earlier than v2.20.6, you will need to update to at least CodeQL
v2.20.6 or disable
<code>actions</code> analysis.</li>
<li>Update default CodeQL bundle version to 2.21.4. <a
href="https://redirect.github.com/github/codeql-action/pull/2910">#2910</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/51f77329afa6477de8c49fc9c7046c15b9a4e79d"><code>51f7732</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/2997">#2997</a>
from github/update-v3.29.5-80a09d7b0</li>
<li><a
href="https://github.com/github/codeql-action/commit/8e90243ddbe0de3f12f4fa361675387b7f94c48d"><code>8e90243</code></a>
Update changelog for v3.29.5</li>
<li><a
href="https://github.com/github/codeql-action/commit/80a09d7b0b5468297f127c81b43cb7335eed0f30"><code>80a09d7</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/2996">#2996</a>
from github/dependabot/npm_and_yarn/npm-240ab9fad0</li>
<li><a
href="https://github.com/github/codeql-action/commit/8388115dc8d6af25bf915cc8455a7d6a77253970"><code>8388115</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/2994">#2994</a>
from github/mergeback/changelog/v3.28.21</li>
<li><a
href="https://github.com/github/codeql-action/commit/401ecaf503b1a19fc0fbd253cc5afe7759870068"><code>401ecaf</code></a>
Merge branch 'main' into mergeback/changelog/v3.28.21</li>
<li><a
href="https://github.com/github/codeql-action/commit/ab5c0c5fa56442a68c2d51b194ccc93faaaaa639"><code>ab5c0c5</code></a>
Merge branch 'main' into dependabot/npm_and_yarn/npm-240ab9fad0</li>
<li><a
href="https://github.com/github/codeql-action/commit/cd264d4dcdc5ee89d8590821e29c66a1bdcaa968"><code>cd264d4</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/2986">#2986</a>
from github/update-bundle/codeql-bundle-v2.22.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/4599055b1e273f63344615ade2c46c852c6d5c63"><code>4599055</code></a>
Merge branch 'main' into update-bundle/codeql-bundle-v2.22.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/fd7ad511e6bd5985ebbc84944e0e173d39a968b8"><code>fd7ad51</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/2971">#2971</a>
from github/update-supported-enterprise-server-versions</li>
<li><a
href="https://github.com/github/codeql-action/commit/ac0c9bfe1e34d6a76860325c1b4abe8208ce98a6"><code>ac0c9bf</code></a>
Merge branch 'main' into
update-supported-enterprise-server-versions</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/d6bbdef45e766d081b84a2def353b0055f728d3e...51f77329afa6477de8c49fc9c7046c15b9a4e79d">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.3&new-version=3.29.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>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-08-01 14:25:23 +01:00
dependabot[bot] 31ade3e496 build(deps): bump actions/ai-inference from 1.1.0 to 1.2.3 (#4006)
Bumps [actions/ai-inference](https://github.com/actions/ai-inference)
from 1.1.0 to 1.2.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/ai-inference/releases">actions/ai-inference's
releases</a>.</em></p>
<blockquote>
<h2>v1.2.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump super-linter/super-linter from 7.4.0 to 8.0.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/ai-inference/pull/62">actions/ai-inference#62</a></li>
<li>Add GitHub Actions workflow for releasing new version by <a
href="https://github.com/sgoedecke"><code>@​sgoedecke</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/59">actions/ai-inference#59</a></li>
<li>Update readme to say MCP needs a PAT by <a
href="https://github.com/sgoedecke"><code>@​sgoedecke</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/60">actions/ai-inference#60</a></li>
<li>Support .prompt.yml files by <a
href="https://github.com/sgoedecke"><code>@​sgoedecke</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/61">actions/ai-inference#61</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/ai-inference/compare/v1.2.2...v1.2.3">https://github.com/actions/ai-inference/compare/v1.2.2...v1.2.3</a></p>
<h2>v1.2.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Fixup bundle by <a
href="https://github.com/sgoedecke"><code>@​sgoedecke</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/58">actions/ai-inference#58</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/ai-inference/compare/v1.2.1...v1.2.2">https://github.com/actions/ai-inference/compare/v1.2.1...v1.2.2</a></p>
<h2>v1.2.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Ensure pkce-challenge is bundled in dist instead of treated as
external by <a
href="https://github.com/sgoedecke"><code>@​sgoedecke</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/57">actions/ai-inference#57</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/ai-inference/compare/v1.2.0...v1.2.1">https://github.com/actions/ai-inference/compare/v1.2.0...v1.2.1</a></p>
<h2>v1.2.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Setup licensed on the codespace by <a
href="https://github.com/maraisr"><code>@​maraisr</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/29">actions/ai-inference#29</a></li>
<li>Bump the npm-development group across 1 directory with 11 updates by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/ai-inference/pull/36">actions/ai-inference#36</a></li>
<li>Update readme by <a
href="https://github.com/sgoedecke"><code>@​sgoedecke</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/41">actions/ai-inference#41</a></li>
<li>Bump <code>@​jest/globals</code> from 29.7.0 to 30.0.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/ai-inference/pull/46">actions/ai-inference#46</a></li>
<li>Make actual inference in CI optional, since it depends on org
settings by <a
href="https://github.com/sgoedecke"><code>@​sgoedecke</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/55">actions/ai-inference#55</a></li>
<li>fix: improve error handling for AI service responses by <a
href="https://github.com/ainoya"><code>@​ainoya</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/49">actions/ai-inference#49</a></li>
<li>Add read-only GitHub MCP support by <a
href="https://github.com/sgoedecke"><code>@​sgoedecke</code></a> in <a
href="https://redirect.github.com/actions/ai-inference/pull/56">actions/ai-inference#56</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/ainoya"><code>@​ainoya</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/ai-inference/pull/49">actions/ai-inference#49</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/ai-inference/compare/v1.1.0...v1.2.0">https://github.com/actions/ai-inference/compare/v1.1.0...v1.2.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/ai-inference/commit/9693b137b6566bb66055a713613bf4f0493701eb"><code>9693b13</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/ai-inference/issues/61">#61</a>
from actions/sgoedecke/prompt-file</li>
<li><a
href="https://github.com/actions/ai-inference/commit/d0b2f23c43311aaad7b4a02894c649a1d6571b53"><code>d0b2f23</code></a>
Merge branch 'main' into sgoedecke/prompt-file</li>
<li><a
href="https://github.com/actions/ai-inference/commit/0df96479bcb4ea24c63144c03be8b6ae7b11003f"><code>0df9647</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/ai-inference/issues/60">#60</a>
from actions/sgoedecke/update-readme</li>
<li><a
href="https://github.com/actions/ai-inference/commit/446f075e3b11fd0afca78c8d0df8d04942161497"><code>446f075</code></a>
Merge branch 'main' into sgoedecke/update-readme</li>
<li><a
href="https://github.com/actions/ai-inference/commit/ce58b26ac7f47baf89dc3d2aeaea560107e25277"><code>ce58b26</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/ai-inference/issues/59">#59</a>
from actions/sgoedecke-patch-1</li>
<li><a
href="https://github.com/actions/ai-inference/commit/1cf96b0212eda48166c54df14085910a7c1f1faf"><code>1cf96b0</code></a>
Merge branch 'main' into sgoedecke/update-readme</li>
<li><a
href="https://github.com/actions/ai-inference/commit/f79e4e11cbdac29de8a9db3b227468308bdc7897"><code>f79e4e1</code></a>
regenerate dist</li>
<li><a
href="https://github.com/actions/ai-inference/commit/72102e50bfcdb3f04447929bf2b2fb23e2db81cf"><code>72102e5</code></a>
Update src/prompt.ts</li>
<li><a
href="https://github.com/actions/ai-inference/commit/2bc30a525a2d4893b4836711c397c64122da23c6"><code>2bc30a5</code></a>
regenerate dist</li>
<li><a
href="https://github.com/actions/ai-inference/commit/8f64ac12840ea5f874555d8a5f663a339e4c3cd6"><code>8f64ac1</code></a>
Fixup types and tests</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/ai-inference/compare/d645f067d89ee1d5d736a5990e327e504d1c5a4a...9693b137b6566bb66055a713613bf4f0493701eb">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/ai-inference&package-manager=github_actions&previous-version=1.1.0&new-version=1.2.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-08-01 14:24:40 +01:00
stirlingbot[bot] dc76840568 🌐 Sync Translations + Update README Progress Table (#4041)
### 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-08-01 14:24:21 +01:00
EthanHealy01andAnthony Stirling 8881f19b03 Tools/ocr/v2 (#4055)
# Description of Changes

- Added the OCR tool 
- Added language mappings file to map selected browser language -> OCR
language and OCR language codes -> english display values. TODO: Use the
translation function to translate the languages rather than mapping them
to english be default
- Added chevron icons to tool step to show expand and collapsed state
more visibly
- Added a re-usable dropdown picker with a footer component

---

## 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.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-08-01 14:22:19 +01:00
Anthony Stirling 8802daf67f Support for SSL testing (#4070)
# 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-08-01 10:26:20 +01:00
Ludy 422af007dc fix(pipeline): allow slashes in pipeline operation values (#4066)
# Description of Changes

- Extended the validation regex for `operation` in the pipeline
processor to allow slashes (`/`), in addition to alphanumeric
characters, underscores, and hyphens.
- This resolves the issue where valid operation strings (e.g., with
subpaths like `/api/v1/general/remove-pages`) were incorrectly rejected.
- Added an explicit log message for better debugging in case of invalid
`operation` values.

---

## 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-07-31 23:58:56 +01:00
Lukas 6879d5fb73 fix: adjust margin of bookmark editor (#4068)
# Description of Changes

- remove overlapping margins of bookmark editor to the checkbox above
- add bottom margin for the bookmark editor element to the "Info"-button
below
- I guess this simply was a typo `margin-top` vs. `margin-bottom` as the
margin to the element above is overlapping

---

## Checklist

### General

- [x] 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)
- [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)

- [x] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)
  *before / after*:
  <p float="left">
<img width="300" alt="before edit with margin highlighted"
src="https://github.com/user-attachments/assets/e7f20a94-8aa0-4f37-96b4-59d1506e1045"
/>
<img width="300" alt="after edit with margin highlighted"
src="https://github.com/user-attachments/assets/3caad04a-0b51-4590-9846-ea9be4985b6e"
/>
  </p>

  before / after with margin highlighted:
  <p float="left">
<img width="300" alt="before edit with margin highlighted"
src="https://github.com/user-attachments/assets/219bd2e4-87c4-4a94-b53f-4c3b730a4da6"
/>
<img width="300" alt="after edit with margin highlighted"
src="https://github.com/user-attachments/assets/5e97f06f-f34e-41b7-98f4-68ced466dca8"
/>
  </p>



### 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.
2025-07-31 23:58:31 +01:00
Peter Dave Hello 91bed18df2 Optimize Dockerfiles (#4069)
# Description of Changes

Summary from GitHub Copilot:

> Optimize Dockerfiles
> 
> This pull request includes updates to multiple Dockerfiles to improve
efficiency, simplify permissions management, and enhance consistency
across development environments. The most important changes involve
optimizing `pip` installations, consolidating `chmod` commands, and
removing redundant script permissions.
> 
> ### Efficiency Improvements:
> * Updated `pip install` commands in `Dockerfile`, `Dockerfile.dev`,
and `Dockerfile.fat` to use the `--no-cache-dir` flag, reducing disk
usage during package installations.
[[1]](diffhunk://#diff-dd2c0eb6ea5cfc6c4bd4eac30934e2d5746747af48fef6da689e85b752f39557L81-R81)
[[2]](diffhunk://#diff-86930c95a19b82f7e64a962a0053d44e855824813019b3698eae4917a90cdcacL39-R39)
[[3]](diffhunk://#diff-571631582b988e88c52c86960cc083b0b8fa63cf88f056f26e9e684195221c27L94-R94)
> 
> ### Permissions Management:
> * Consolidated `chmod` commands for scripts in `Dockerfile.dev` to
simplify permissions setup. Combined `git-init.sh` and `init-setup.sh`
into a single command.
> * Removed redundant `chmod +x` for `init.sh` in `Dockerfile` and
`Dockerfile.fat`, as permissions for `/scripts/*` already cover this
file.
[[1]](diffhunk://#diff-dd2c0eb6ea5cfc6c4bd4eac30934e2d5746747af48fef6da689e85b752f39557L92)
[[2]](diffhunk://#diff-571631582b988e88c52c86960cc083b0b8fa63cf88f056f26e9e684195221c27L105)

---

## 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-07-31 23:57:09 +01:00
Anthony Stirling ae5e87726d Version bump + docker ultra lite fix (#4057)
# 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-07-31 06:26:27 +01:00
1274dc9279 fix(pipeline): correct paths for pipeline & support default WebUI pipeline config extraction (#4051)
# Description of Changes

- **What was changed:**  
- Updated `.github/labeler-config-srvaroa.yml` to include
`app/core/src/main/resources/static/pipeline/defaultWebUIConfigs/**`
under the labeler paths.
- Removed `COPY pipeline /pipeline` from all three Dockerfiles to slim
down images.
- Added a new `PIPELINE_PATH` constant and `getPipelinePath()` method in
`InstallationPathConfig.java`.
- Implemented `GeneralUtils.extractPipeline()` to copy default pipeline
JSON configs (`OCR images.json`, `Prepare-pdfs-for-email.json`,
`split-rotate-auto-rename.json`) from classpath into the installation
directory.
- Invoked `GeneralUtils.extractPipeline()` during initial setup in
`InitialSetup.java`.
  - Updated `.gitignore` to treat `./pipeline/` as ignored.  

- **Why the change was made:**  
Ensures that default WebUI pipeline configurations are automatically
extracted at runtime rather than baked into the image, improving
flexibility and reducing image size.

---

## 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.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-30 22:40:21 +01:00
LudyandCopilot 9bb08eed5a fix(search): add null-check in dropdown hide handler (#3983)
# Description of Changes

- **What was changed**  
- Added a null-check on `dropdownMenu` in the document click handler to
prevent errors when the menu element is not present.

- **Why the change was made**  
- To guard against potential runtime errors if `dropdownMenu` is `null`.

```js
search.js:126 Uncaught TypeError: Cannot read properties of null (reading 'classList')
    at HTMLDocument.<anonymous> (search.js:126:60)
(anonymous)	@	search.js:126
```

---

## 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.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-30 22:38:43 +01:00
Ludy 64a471c3a9 fix(workflows): correct YAML indentation in frontend license update workflow (#4056)
…orkflow

# Description of Changes

- Fixed invalid YAML syntax caused by inconsistent indentation inside a
multi-line JavaScript template string in
`.github/workflows/frontend-licenses-update.yml`.
- The incorrect indentation broke the GitHub Actions runner with a
syntax error on line 131.
- This change ensures that the entire `commentBody = \`...\`;` block is
treated as a valid JavaScript string inside the `run:` shell context.


---

## 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-07-30 22:38:03 +01:00
Anthony Stirling fc89ee2c78 Update docker-compose.fat.yml 2025-07-30 11:19:28 +01:00
Anthony Stirling a6df5e4246 Update docker-compose.yml 2025-07-30 11:19:13 +01:00
Anthony Stirling 8fb78d612b remove file locks plus formatting (#4049)
# 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-07-29 23:19:46 +01:00
Anthony Stirling 989052b099 Update deploy-on-v2-commit.yml 2025-07-29 16:00:08 +01:00
Anthony Stirling dbe84f7e96 Update deploy-on-v2-commit.yml 2025-07-29 15:50:36 +01:00
Anthony Stirling 8989bdb211 Revert "V2 Update PR-Auto-Deploy-V2.yml to use common tag" (#4047)
Reverts Stirling-Tools/Stirling-PDF#4046
2025-07-29 15:49:27 +01:00
Anthony Stirling 1bd166f6fb V2 Update PR-Auto-Deploy-V2.yml to use common tag (#4046)
# 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-07-29 15:38:37 +01:00
Anthony Stirlinganda 75bd154a82 V2 async requests (#4043)
# 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.

---------

Co-authored-by: a <a>
2025-07-29 13:44:17 +01:00
Anthony Stirling 1ce813bcd8 Endpoint UI data for V2 (#4044)
# Description of Changes
This pull request introduces several enhancements and new features,
primarily focused on improving API documentation support, expanding
backend functionality, and adding new frontend tools. Key changes
include the integration of Swagger API documentation, the addition of a
new `UIDataController` for backend data handling, and updates to the
frontend to include a Swagger UI tool.

### Backend Enhancements:
* **Swagger API Documentation Integration**:
- Added support for dynamically configuring Swagger servers using the
`SWAGGER_SERVER_URL` environment variable in `OpenApiConfig`
(`[app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.javaR54-L63](diffhunk://#diff-6080fb3dc14efc430c9de1bf9fa4996a23deebc14230dde7788949b2c49cca68R54-L63)`).
- Imported necessary Swagger dependencies
(`[app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.javaR13](diffhunk://#diff-6080fb3dc14efc430c9de1bf9fa4996a23deebc14230dde7788949b2c49cca68R13)`).
- Updated `nginx.conf` to proxy Swagger-related requests to the backend
(`[docker/frontend/nginx.confR55-R92](diffhunk://#diff-6d35fafb4405bd052c6d5e48bd946bcef7c77552a74e1b902de45e85eee09aceR55-R92)`).

* **New `UIDataController`**:
- Introduced a new controller (`UIDataController`) to serve React UI
data, including endpoints for home data, licenses, pipeline
configurations, signature data, and OCR data
(`[app/core/src/main/java/stirling/software/SPDF/controller/api/UIDataController.javaR1-R301](diffhunk://#diff-3e7063d4e921c7b9e6eedfcad0e535ba3eff68476dcff5e6f28b00c388cff646R1-R301)`).

* **Endpoint Handling**:
- Modified `ConfigController` to include explicit parameter naming for
better clarity in API requests
(`[app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.javaL113-R120](diffhunk://#diff-43d19d45ae547fd79090596c06d58cb0eb7f722ed43eb4da59f9dec39f6def6eL113-R120)`).

### Frontend Enhancements:
* **Swagger UI Tool**:
- Added a new tool definition (`swagger`) in `useToolManagement.tsx`,
with an icon and lazy-loaded component
(`[frontend/src/hooks/useToolManagement.tsxR30-R38](diffhunk://#diff-57f8a6b3e75ecaec10ad445b01afe8fccc376af6f8ad4d693c68cf98e8863273R30-R38)`).
- Implemented the `SwaggerUI` component to open the Swagger
documentation in a new tab
(`[frontend/src/tools/SwaggerUI.tsxR1-R18](diffhunk://#diff-ca9bdf83c5d611a5edff10255103d7939895635b33a258dd77db6571da6c4600R1-R18)`).

* **Localization Updates**:
- Updated English (US and GB) translation files to include
Swagger-related strings
(`[[1]](diffhunk://#diff-14c707e28788a3a84ed5293ff6689be73d4bca00e155beaf090f9b37c978babbR578-R581)`,
`[[2]](diffhunk://#diff-14c707e28788a3a84ed5293ff6689be73d4bca00e155beaf090f9b37c978babbR1528-R1533)`,
`[[3]](diffhunk://#diff-e4d543afa388d9eb8a423e45dfebb91641e3558d00848d70b285ebb91c40b249R578-R581)`,
`[[4]](diffhunk://#diff-e4d543afa388d9eb8a423e45dfebb91641e3558d00848d70b285ebb91c40b249R1528-R1533)`).

### Workflow Updates:
* **Environment Variable Additions**:
- Added `SWAGGER_SERVER_URL` to the `PR-Auto-Deploy-V2.yml` and
`deploy-on-v2-commit.yml` workflows for configuring Swagger server URLs
(`[[1]](diffhunk://#diff-931fcb06ba030420d7044dde06465ad55b4e769a9bd374dcd6a0c76f79a5e30eR320)`,
`[[2]](diffhunk://#diff-f8b6ec3c0af9cd2d8dffef6f3def2be6357fe596a606850ca7f5d799e1349069R150)`).
<!--
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-07-29 13:11:53 +01:00
85f5cccf04 V2 settings api (Added to V1) (#4015)
# 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.

---------

Co-authored-by: a <a>
Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2025-07-29 13:02:02 +01:00
stirlingbot[bot] 043db37dfb 🤖 format everything with pre-commit by stirlingbot (#4040)
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-07-28 10:15:17 +01:00
stirlingbot[bot] 5877fd5d0c Update 3rd Party Licenses (#4012)
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-07-28 10:14:45 +01:00
Peter Dave Hello 79727ac7f5 Update and improve zh_TW Traditional Chinese translation (#4038)
# Description of Changes

Update and improve zh_TW Traditional Chinese translation

---

## 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)
- [x] 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)
- [x] 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-07-28 10:14:30 +01:00
Philip H. b94e86ad0f legal: termsAndConditions has moved (#4035)
https://www.stirlingpdf.com/terms

# 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

- [x] 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)
- [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-07-28 10:14:20 +01:00
Ludy 054bf15b24 ci(workflows): add concurrency cancellation to CI workflows (#4034)
# Description of Changes

- **What was changed**  
Added a `concurrency` block to all GitHub Actions workflow files
(`build.yml`, `check_properties.yml`, `licenses-update.yml`,
`push-docker.yml`, `sonarqube.yml`, `swagger.yml`, `sync_files.yml`,
`testdriver.yml`) to group jobs by workflow name, event name, and
branch, with `cancel-in-progress: true`.

- **Why the change was made**  
To prevent redundant runs of long-running CI jobs on the same branch
when new commits or PR updates occur, saving compute resources and
reducing queue times.


---

## 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-07-26 16:46:25 +01:00
Peter Dave Hello e3cdf5f729 Translate language names for zh_TW Traditional Chinese (#4036)
# Description of Changes

Update zh_TW Traditional Chinese translation

---

## 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)
- [x] 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)
- [x] 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-07-26 16:43:58 +01:00
Anthony Stirling 3b82602744 Deploy on v2 commit (#4033)
# 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-07-25 14:40:58 +01:00
EthanHealy01 a9a41b3877 V2: Design Navbar (#4017)
# Description of Changes

- Changed the navbar styling to include all the icons that were on our
figma design
- Added a new link to our index.html to include the MUI symbols. 
- I chose to keep the automate and read icons the same as they were
already because I feel as though they make more sense

```
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,700,0,0" />
```
---

figma vs app dark
<img width="422" height="1038" alt="Screenshot 2025-07-21 at 5 44 19 PM"
src="https://github.com/user-attachments/assets/15d5583f-ce3c-418f-81c6-6e6022f5f4d0"
/>

figma vs app light
<img width="244" height="926" alt="Screenshot 2025-07-21 at 5 57 27 PM"
src="https://github.com/user-attachments/assets/c855d02b-20ee-4ccf-af1f-a3c1a4c2a154"
/>



## Checklist

### General

- [x] 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)
- [x] 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)

- [x] 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.
2025-07-25 12:27:30 +01:00
EthanHealy01 5f7e578ff8 change logos, favicon and browser tab title (#4032)
# Description of Changes

- changed favicon and other logos in public folder
- changes the title title tag in the index.html to "Stirling PDF"

---

## 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-07-25 12:14:37 +01:00
Reece Browne e4d480c7b3 Feature/v2/multiselect (#4024)
# Description of Changes
This pull request introduces significant updates to the file selection
logic, tool rendering, and file context management in the frontend
codebase. The changes aim to improve modularity, enhance
maintainability, and streamline the handling of file-related operations.
Key updates include the introduction of a new `FileSelectionContext`,
refactoring of file selection logic, and updates to tool management and
rendering.

### File Selection Context and Logic Refactor:
* Added a new `FileSelectionContext` to centralize file selection state
and provide utility hooks for managing selected files, selection limits,
and tool mode. (`frontend/src/contexts/FileSelectionContext.tsx`,
[frontend/src/contexts/FileSelectionContext.tsxR1-R77](diffhunk://#diff-bda35f1aaa5eafa0a0dc48e0b1270d862f6da360ba1241234e891f0ca8907327R1-R77))
* Replaced local file selection logic in `FileEditor` with context-based
logic, improving consistency and reducing duplication.
(`frontend/src/components/fileEditor/FileEditor.tsx`,
[[1]](diffhunk://#diff-481d0a2d8a1714d34d21181db63a020b08dfccfbfa80bf47ac9af382dff25310R63-R70)
[[2]](diffhunk://#diff-481d0a2d8a1714d34d21181db63a020b08dfccfbfa80bf47ac9af382dff25310R404-R438)

### Tool Management and Rendering:
* Refactored `ToolRenderer` to use a `Suspense` fallback for lazy-loaded
tools, improving user experience during tool loading.
(`frontend/src/components/tools/ToolRenderer.tsx`,
[frontend/src/components/tools/ToolRenderer.tsxL32-L64](diffhunk://#diff-2083701113aa92cd1f5ce1b4b52cc233858e31ed7bcf39c5bfb1bcc34e99b6a9L32-L64))
* Simplified `ToolPicker` by reusing the `ToolRegistry` type, reducing
redundancy. (`frontend/src/components/tools/ToolPicker.tsx`,
[frontend/src/components/tools/ToolPicker.tsxL4-R4](diffhunk://#diff-e47deca9132018344c159925f1264794acdd57f4b65e582eb9b2a4ea69ec126dL4-R4))

### File Context Enhancements:
* Introduced a utility function `getFileId` for consistent file ID
extraction, replacing repetitive inline logic.
(`frontend/src/contexts/FileContext.tsx`,
[[1]](diffhunk://#diff-95b3d103fa434f81fdae55f2ea14eda705f0def45a0f2c5754f81de6f2fd93bcR25)
[[2]](diffhunk://#diff-95b3d103fa434f81fdae55f2ea14eda705f0def45a0f2c5754f81de6f2fd93bcL101-R102)
* Updated `FileContextProvider` to use more specific types for PDF
documents, enhancing type safety.
(`frontend/src/contexts/FileContext.tsx`,
[[1]](diffhunk://#diff-95b3d103fa434f81fdae55f2ea14eda705f0def45a0f2c5754f81de6f2fd93bcL350-R351)
[[2]](diffhunk://#diff-95b3d103fa434f81fdae55f2ea14eda705f0def45a0f2c5754f81de6f2fd93bcL384-R385)

### Compression Tool Enhancements:
* Added blob URL cleanup logic to the compression hook to prevent memory
leaks. (`frontend/src/hooks/tools/compress/useCompressOperation.ts`,
[frontend/src/hooks/tools/compress/useCompressOperation.tsR58-L66](diffhunk://#diff-d7815fea0e89989511ae1786f7031cba492b9f2db39b7ade92d9736d1bd4b673R58-L66))
* Adjusted file ID generation in the compression operation to handle
multiple files more effectively.
(`frontend/src/hooks/tools/compress/useCompressOperation.ts`,
[frontend/src/hooks/tools/compress/useCompressOperation.tsL90-R102](diffhunk://#diff-d7815fea0e89989511ae1786f7031cba492b9f2db39b7ade92d9736d1bd4b673L90-R102))

---

## 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-07-25 09:37:52 +01:00
7d6b70871b url fixes for access issues (#4013)
# Description of Changes


This pull request introduces a new SSRF (Server-Side Request Forgery)
protection mechanism for URL handling in the application. Key changes
include adding a dedicated `SsrfProtectionService`, integrating
SSRF-safe policies into HTML sanitization, and extending application
settings to support configurable URL security options.

### SSRF Protection Implementation:
* **`SsrfProtectionService`**: Added a new service to handle SSRF
protection with configurable levels (`OFF`, `MEDIUM`, `MAX`) and checks
for private networks, localhost, link-local addresses, and cloud
metadata endpoints
(`app/common/src/main/java/stirling/software/common/service/SsrfProtectionService.java`).

### Application Configuration Enhancements:
* **`ApplicationProperties`**: Introduced a new `Html` configuration
class with nested `UrlSecurity` settings, allowing fine-grained control
over URL security, including allowed/blocked domains and internal TLDs
(`app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java`).
[[1]](diffhunk://#diff-1c357db0a3e88cf5bedd4a5852415fadad83b8b3b9eb56e67059d8b9d8b10702R293)
[[2]](diffhunk://#diff-1c357db0a3e88cf5bedd4a5852415fadad83b8b3b9eb56e67059d8b9d8b10702R346-R364)
* **`settings.yml.template`**: Updated the configuration template to
include the new `html.urlSecurity` settings, enabling users to customize
SSRF protection behavior
(`app/core/src/main/resources/settings.yml.template`).

### HTML Sanitization Updates:
* **`CustomHtmlSanitizer`**: Integrated SSRF-safe URL validation into
the HTML sanitizer by using the `SsrfProtectionService`. Added a custom
policy for validating `img` tags' `src` attributes
(`app/common/src/main/java/stirling/software/common/util/CustomHtmlSanitizer.java`).

---

## 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.

---------

Co-authored-by: a <a>
Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-24 13:53:21 +01:00
4e5f595951 Feature/V2/Compress (#3982)
# Description of Changes

- Added the compression tool
- Slightly adjusted the padding on split, compress and the parent
sidebar component so things aren't so smushed
- Future work will be to allow multi-select on files and to add further
styling to the toolstep / sidebar component

---

## 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.

---------

Co-authored-by: Ethan <ethan@MacBook-Pro.local>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2025-07-22 11:01:28 +01:00
dependabot[bot] c161000f85 build(deps): bump com.diffplug.spotless from 7.1.0 to 7.2.1 (#4019)
Bumps com.diffplug.spotless from 7.1.0 to 7.2.1.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.diffplug.spotless&package-manager=gradle&previous-version=7.1.0&new-version=7.2.1)](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-07-22 10:14:34 +01:00
dependabot[bot] b650d443a7 build(deps): bump springSecuritySamlVersion from 6.5.1 to 6.5.2 (#4020)
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-07-22 10:14:23 +01:00
dependabot[bot] d80c11dffa build(deps): bump sigstore/cosign-installer from 3.9.1 to 3.9.2 (#4009)
[//]: # (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
[sigstore/cosign-installer](https://github.com/sigstore/cosign-installer)
from 3.9.1 to 3.9.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sigstore/cosign-installer/releases">sigstore/cosign-installer's
releases</a>.</em></p>
<blockquote>
<h2>v3.9.2</h2>
<h2>What's Changed</h2>
<ul>
<li>not fail fast and setup permissions in <a
href="https://redirect.github.com/sigstore/cosign-installer/pull/195">sigstore/cosign-installer#195</a></li>
<li>drop old unsupported versions &lt;v2.0.0 in <a
href="https://redirect.github.com/sigstore/cosign-installer/pull/192">sigstore/cosign-installer#192</a></li>
<li>Update default to v2.5.3 in <a
href="https://redirect.github.com/sigstore/cosign-installer/pull/196">sigstore/cosign-installer#196</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sigstore/cosign-installer/compare/v3.9.1...v3.9.2">https://github.com/sigstore/cosign-installer/compare/v3.9.1...v3.9.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sigstore/cosign-installer/commit/d58896d6a1865668819e1d91763c7751a165e159"><code>d58896d</code></a>
Update default to v2.5.3 (<a
href="https://redirect.github.com/sigstore/cosign-installer/issues/196">#196</a>)</li>
<li><a
href="https://github.com/sigstore/cosign-installer/commit/e40248c492a99ad409432e2ea978d7a2811f2e1f"><code>e40248c</code></a>
drop old unsupported versions &lt;v2.0.0 (<a
href="https://redirect.github.com/sigstore/cosign-installer/issues/192">#192</a>)</li>
<li><a
href="https://github.com/sigstore/cosign-installer/commit/d9374b96fed791ab117111a9a307a92b68bf3145"><code>d9374b9</code></a>
not fail fast and setup permissions (<a
href="https://redirect.github.com/sigstore/cosign-installer/issues/195">#195</a>)</li>
<li>See full diff in <a
href="https://github.com/sigstore/cosign-installer/compare/398d4b0eeef1380460a10c8013a76f728fb906ac...d58896d6a1865668819e1d91763c7751a165e159">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sigstore/cosign-installer&package-manager=github_actions&previous-version=3.9.1&new-version=3.9.2)](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-07-22 10:11:29 +01:00
dependabot[bot]andLudy c80aaf6cd2 build(deps): bump actions/checkout from 2.4.2 to 4.2.2 (#4010)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.2
to 4.2.2.
<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>v4.2.2</h2>
<h2>What's Changed</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>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.1...v4.2.2">https://github.com/actions/checkout/compare/v4.2.1...v4.2.2</a></p>
<h2>v4.2.1</h2>
<h2>What's Changed</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>New Contributors</h2>
<ul>
<li><a href="https://github.com/Jcambass"><code>@​Jcambass</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1919">actions/checkout#1919</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.0...v4.2.1">https://github.com/actions/checkout/compare/v4.2.0...v4.2.1</a></p>
<h2>v4.2.0</h2>
<h2>What's Changed</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>Dependabot updates in <a
href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>
&amp; <a
href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/yasonk"><code>@​yasonk</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1869">actions/checkout#1869</a></li>
<li><a href="https://github.com/lucacome"><code>@​lucacome</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.1.7...v4.2.0">https://github.com/actions/checkout/compare/v4.1.7...v4.2.0</a></p>
<h2>v4.1.7</h2>
<h2>What's Changed</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>New Contributors</h2>
<ul>
<li><a href="https://github.com/orhantoy"><code>@​orhantoy</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.1.6...v4.1.7">https://github.com/actions/checkout/compare/v4.1.6...v4.1.7</a></p>
<h2>v4.1.6</h2>
<h2>What's Changed</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>
<li>Update for 4.1.6 release by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1733">actions/checkout#1733</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.1.5...v4.1.6">https://github.com/actions/checkout/compare/v4.1.5...v4.1.6</a></p>
<h2>v4.1.5</h2>
<h2>What's Changed</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</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>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>
<ul>
<li>Check git version before attempting to disable
<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/1656">actions/checkout#1656</a></li>
<li>Add SSH user parameter by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1685">actions/checkout#1685</a></li>
<li>Update <code>actions/checkout</code> version in
<code>update-main-version.yml</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1650">actions/checkout#1650</a></li>
</ul>
<h2>v4.1.2</h2>
<ul>
<li>Fix: Disable sparse checkout whenever <code>sparse-checkout</code>
option is not present <a
href="https://github.com/dscho"><code>@​dscho</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1598">actions/checkout#1598</a></li>
</ul>
<h2>v4.1.1</h2>
<ul>
<li>Correct link to GitHub Docs by <a
href="https://github.com/peterbe"><code>@​peterbe</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1511">actions/checkout#1511</a></li>
<li>Link to release page from what's new section by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1514">actions/checkout#1514</a></li>
</ul>
<h2>v4.1.0</h2>
<ul>
<li><a href="https://redirect.github.com/actions/checkout/pull/1396">Add
support for partial checkout filters</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/checkout/commit/11bd71901bbe5b1630ceea73d27597364c9af683"><code>11bd719</code></a>
Prepare 4.2.2 Release (<a
href="https://redirect.github.com/actions/checkout/issues/1953">#1953</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/e3d2460bbb42d7710191569f88069044cfb9d8cf"><code>e3d2460</code></a>
Expand unit test coverage (<a
href="https://redirect.github.com/actions/checkout/issues/1946">#1946</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/163217dfcd28294438ea1c1c149cfaf66eec283e"><code>163217d</code></a>
<code>url-helper.ts</code> now leverages well-known environment
variables. (<a
href="https://redirect.github.com/actions/checkout/issues/1941">#1941</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871"><code>eef6144</code></a>
Prepare 4.2.1 release (<a
href="https://redirect.github.com/actions/checkout/issues/1925">#1925</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/6b42224f41ee5dfe5395e27c8b2746f1f9955030"><code>6b42224</code></a>
Add workflow file for publishing releases to immutable action package
(<a
href="https://redirect.github.com/actions/checkout/issues/1919">#1919</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/de5a000abf73b6f4965bd1bcdf8f8d94a56ea815"><code>de5a000</code></a>
Check out other refs/* by commit if provided, fall back to ref (<a
href="https://redirect.github.com/actions/checkout/issues/1924">#1924</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/d632683dd7b4114ad314bca15554477dd762a938"><code>d632683</code></a>
Prepare 4.2.0 release (<a
href="https://redirect.github.com/actions/checkout/issues/1878">#1878</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/6d193bf28034eafb982f37bd894289fe649468fc"><code>6d193bf</code></a>
Bump braces from 3.0.2 to 3.0.3 (<a
href="https://redirect.github.com/actions/checkout/issues/1777">#1777</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/db0cee9a514becbbd4a101a5fbbbf47865ee316c"><code>db0cee9</code></a>
Bump the minor-npm-dependencies group across 1 directory with 4 updates
(<a
href="https://redirect.github.com/actions/checkout/issues/1872">#1872</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/b6849436894e144dbce29d7d7fda2ae3bf9d8365"><code>b684943</code></a>
Add Ref and Commit outputs (<a
href="https://redirect.github.com/actions/checkout/issues/1180">#1180</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/checkout/compare/v2.4.2...11bd71901bbe5b1630ceea73d27597364c9af683">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=2.4.2&new-version=4.2.2)](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>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
2025-07-22 10:11:10 +01:00
dependabot[bot] 28e95438b3 build(deps): bump github/codeql-action from 3.29.2 to 3.29.3 (#4008)
Bumps [github/codeql-action](https://github.com/github/codeql-action)
from 3.29.2 to 3.29.3.
<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.29.3</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.29.3 - 21 Jul 2025</h2>
<p>No user facing changes.</p>
<p>See the full <a
href="https://github.com/github/codeql-action/blob/v3.29.3/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.29.3 - 21 Jul 2025</h2>
<p>No user facing changes.</p>
<h2>3.29.2 - 30 Jun 2025</h2>
<ul>
<li>Experimental: When the <code>quality-queries</code> input for the
<code>init</code> action is provided with an argument, separate
<code>.quality.sarif</code> files are produced and uploaded for each
language with the results of the specified queries. Do not use this in
production as it is part of an internal experiment and subject to change
at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/2935">#2935</a></li>
</ul>
<h2>3.29.1 - 27 Jun 2025</h2>
<ul>
<li>Fix bug in PR analysis where user-provided <code>include</code>
query filter fails to exclude non-included queries. <a
href="https://redirect.github.com/github/codeql-action/pull/2938">#2938</a></li>
<li>Update default CodeQL bundle version to 2.22.1. <a
href="https://redirect.github.com/github/codeql-action/pull/2950">#2950</a></li>
</ul>
<h2>3.29.0 - 11 Jun 2025</h2>
<ul>
<li>Update default CodeQL bundle version to 2.22.0. <a
href="https://redirect.github.com/github/codeql-action/pull/2925">#2925</a></li>
<li>Bump minimum CodeQL bundle version to 2.16.6. <a
href="https://redirect.github.com/github/codeql-action/pull/2912">#2912</a></li>
</ul>
<h2>3.28.20 - 21 July 2025</h2>
<ul>
<li>Remove support for combining SARIF files from a single upload for
GHES 3.18, see <a
href="https://github.blog/changelog/2024-05-06-code-scanning-will-stop-combining-runs-from-a-single-upload/">the
changelog post</a>. <a
href="https://redirect.github.com/github/codeql-action/pull/2959">#2959</a></li>
</ul>
<h2>3.28.19 - 03 Jun 2025</h2>
<ul>
<li>The CodeQL Action no longer includes its own copy of the extractor
for the <code>actions</code> language, which is currently in public
preview.
The <code>actions</code> extractor has been included in the CodeQL CLI
since v2.20.6. If your workflow has enabled the <code>actions</code>
language <em>and</em> you have pinned
your <code>tools:</code> property to a specific version of the CodeQL
CLI earlier than v2.20.6, you will need to update to at least CodeQL
v2.20.6 or disable
<code>actions</code> analysis.</li>
<li>Update default CodeQL bundle version to 2.21.4. <a
href="https://redirect.github.com/github/codeql-action/pull/2910">#2910</a></li>
</ul>
<h2>3.28.18 - 16 May 2025</h2>
<ul>
<li>Update default CodeQL bundle version to 2.21.3. <a
href="https://redirect.github.com/github/codeql-action/pull/2893">#2893</a></li>
<li>Skip validating SARIF produced by CodeQL for improved performance.
<a
href="https://redirect.github.com/github/codeql-action/pull/2894">#2894</a></li>
<li>The number of threads and amount of RAM used by CodeQL can now be
set via the <code>CODEQL_THREADS</code> and <code>CODEQL_RAM</code>
runner environment variables. If set, these environment variables
override the <code>threads</code> and <code>ram</code> inputs
respectively. <a
href="https://redirect.github.com/github/codeql-action/pull/2891">#2891</a></li>
</ul>
<h2>3.28.17 - 02 May 2025</h2>
<ul>
<li>Update default CodeQL bundle version to 2.21.2. <a
href="https://redirect.github.com/github/codeql-action/pull/2872">#2872</a></li>
</ul>
<h2>3.28.16 - 23 Apr 2025</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/d6bbdef45e766d081b84a2def353b0055f728d3e"><code>d6bbdef</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/2977">#2977</a>
from github/update-v3.29.3-7710ed11e</li>
<li><a
href="https://github.com/github/codeql-action/commit/210cc9bfa2103f4b7c4701ee383183b944c62578"><code>210cc9b</code></a>
Update changelog for v3.29.3</li>
<li><a
href="https://github.com/github/codeql-action/commit/7710ed11e398ea99c7f7004c2b2e0f580458db42"><code>7710ed1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/2970">#2970</a>
from github/cklin/diff-informed-feature-enable</li>
<li><a
href="https://github.com/github/codeql-action/commit/6a49a8cbce6ecbd74ea251a48dbc84e64ce3be4d"><code>6a49a8c</code></a>
build: refresh js files</li>
<li><a
href="https://github.com/github/codeql-action/commit/3aef4108d1730e17b6fd24f8b9c49d8fcc87d46d"><code>3aef410</code></a>
Add diff-informed-analysis-utils.test.ts</li>
<li><a
href="https://github.com/github/codeql-action/commit/614b64c6ec97a4ad54f7c99c5becbf593144dbfb"><code>614b64c</code></a>
Diff-informed analysis: disable for GHES below 3.19</li>
<li><a
href="https://github.com/github/codeql-action/commit/aefb854fe5563f4650638224c839c6e9b33c25b5"><code>aefb854</code></a>
Feature.DiffInformedQueries: default to true</li>
<li><a
href="https://github.com/github/codeql-action/commit/03a2a17e75d20e4ff461b43f161fb2b52165f632"><code>03a2a17</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/2967">#2967</a>
from github/cklin/overlay-feature-flags</li>
<li><a
href="https://github.com/github/codeql-action/commit/07455ed3c36f739ad76d1c4e55f8b49550f74344"><code>07455ed</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/2972">#2972</a>
from github/koesie10/ghes-satisfies</li>
<li><a
href="https://github.com/github/codeql-action/commit/3fb562ddcce3ca92b83ea1bb7abaa579a1ab882d"><code>3fb562d</code></a>
build: refresh js files</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/181d5eefc20863364f96762470ba6f862bdef56b...d6bbdef45e766d081b84a2def353b0055f728d3e">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.2&new-version=3.29.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-07-22 10:10:56 +01:00
dependabot[bot] b1bbad53bc build(deps): bump step-security/harden-runner from 2.12.2 to 2.13.0 (#4007)
Bumps
[step-security/harden-runner](https://github.com/step-security/harden-runner)
from 2.12.2 to 2.13.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/step-security/harden-runner/releases">step-security/harden-runner's
releases</a>.</em></p>
<blockquote>
<h2>v2.13.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Improved job markdown summary</li>
<li>Https monitoring for all domains (included with the enterprise
tier)</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/step-security/harden-runner/compare/v2...v2.13.0">https://github.com/step-security/harden-runner/compare/v2...v2.13.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/step-security/harden-runner/commit/ec9f2d5744a09debf3a187a3f4f675c53b671911"><code>ec9f2d5</code></a>
Merge pull request <a
href="https://redirect.github.com/step-security/harden-runner/issues/565">#565</a>
from step-security/rc-24</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/04bcbc31cfcefe0cf4720832008735021cec5ec4"><code>04bcbc3</code></a>
update agent</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/7c7a56fcaa124ab72fff1cc3e81257f264fd7317"><code>7c7a56f</code></a>
feat: get job summary from API</li>
<li>See full diff in <a
href="https://github.com/step-security/harden-runner/compare/6c439dc8bdf85cadbbce9ed30d1c7b959517bc49...ec9f2d5744a09debf3a187a3f4f675c53b671911">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=step-security/harden-runner&package-manager=github_actions&previous-version=2.12.2&new-version=2.13.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-07-22 10:10:45 +01:00
dependabot[bot] ea9b27719f build(deps): bump alpine from 3.22.0 to 3.22.1 (#4011)
Bumps alpine from 3.22.0 to 3.22.1.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=alpine&package-manager=docker&previous-version=3.22.0&new-version=3.22.1)](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-07-22 10:10:26 +01:00
dependabot[bot] 0b4913c6e4 build(deps): bump commons-io:commons-io from 2.19.0 to 2.20.0 (#4003)
Bumps [commons-io:commons-io](https://github.com/apache/commons-io) from
2.19.0 to 2.20.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/apache/commons-io/blob/master/RELEASE-NOTES.txt">commons-io:commons-io's
changelog</a>.</em></p>
<blockquote>
<p>Apache Commons IO 2.20.0 Release Notes</p>
<p>The Apache Commons IO team is pleased to announce the release of
Apache Commons IO 2.20.0.</p>
<h2>Introduction</h2>
<p>The Apache Commons IO library contains utility classes, stream
implementations, file filters,
file comparators, endian transformation classes, and much more.</p>
<p>Version 2.19.1: Java 8 or later is required.</p>
<h2>New features</h2>
<p>o IO-875: Add
org.apache.commons.io.file.CountingPathVisitor.accept(Path,
BasicFileAttributes) <a
href="https://redirect.github.com/apache/commons-io/issues/743">#743</a>.
Thanks to Pierre Baumard, Gary Gregory.
o Add org.apache.commons.io.Charsets.isAlias(Charset, String). Thanks to
Gary Gregory.
o Add org.apache.commons.io.Charsets.isUTF8(Charset). Thanks to Gary
Gregory.
o Add org.apache.commons.io.Charsets.toCharsetDefault(String, Charset).
Thanks to Gary Gregory.
o IO-279: Add Tailer ignoreTouch option <a
href="https://redirect.github.com/apache/commons-io/issues/757">#757</a>.
Thanks to Joerg Budischewski, Gary Gregory.</p>
<h2>Fixed Bugs</h2>
<p>o [javadoc] Rename parameter of ProxyOutputStream.write(int) <a
href="https://redirect.github.com/apache/commons-io/issues/740">#740</a>.
Thanks to Jesse Glick.
o IO-875: CopyDirectoryVisitor ignores fileFilter <a
href="https://redirect.github.com/apache/commons-io/issues/743">#743</a>.
Thanks to Pierre Baumard, Gary Gregory.
o org.apache.commons.io.build.AbstractOrigin.getReader(Charset) now maps
a null Charset to the default Charset. Thanks to Gary Gregory.
o
org.apache.commons.io.build.AbstractOrigin.AbstractRandomAccessFileOrigin.getReader(Charset)
now maps a null Charset to the default Charset. Thanks to Gary Gregory.
o
org.apache.commons.io.build.AbstractOrigin.ByeArrayOrigin.getReader(Charset)
now maps a null Charset to the default Charset. Thanks to Gary Gregory.
o
org.apache.commons.io.build.AbstractOrigin.InputStreamOrigin.getReader(Charset)
now maps a null Charset to the default Charset. Thanks to Gary Gregory.
o org.apache.commons.io.build.AbstractOrigin.getWriter(Charset) now maps
a null Charset to the default Charset. Thanks to Gary Gregory.
o
org.apache.commons.io.build.AbstractOrigin.AbstractRandomAccessFileOrigin.getWriter(Charset)
now maps a null Charset to the default Charset. Thanks to Gary Gregory.
o
org.apache.commons.io.build.AbstractOrigin.OutputStreamOrigin.getWriter(Charset)
now maps a null Charset to the default Charset. Thanks to Gary Gregory.
o FileUtils.readLines(File, Charset) now maps a null Charset to the
default Charset <a
href="https://redirect.github.com/apache/commons-io/issues/744">#744</a>.
Thanks to Ryan Kurtz, Gary Gregory.
o Fix SpotBugs [ERROR] Medium: Shared primitive variable
&quot;atSlashCr&quot; in one thread may not yield the value of the most
recent write from another thread
[org.apache.commons.io.input.WindowsLineEndingInputStream,
org.apache.commons.io.input.WindowsLineEndingInputStream] At
WindowsLineEndingInputStream.java:[line 77]Another occurrence at
WindowsLineEndingInputStream.java:[line 81]
AT_STALE_THREAD_WRITE_OF_PRIMITIVE. Thanks to Gary Gregory.
o Fix SpotBugs [ERROR] Medium: Shared primitive variable
&quot;atSlashCr&quot; in one thread may not yield the value of the most
recent write from another thread
[org.apache.commons.io.input.WindowsLineEndingInputStream] At
WindowsLineEndingInputStream.java:[line 112]
AT_STALE_THREAD_WRITE_OF_PRIMITIVE. Thanks to Gary Gregory.
o Fix SpotBugs [ERROR] Medium: Shared primitive variable
&quot;atSlashLf&quot; in one thread may not yield the value of the most
recent write from another thread
[org.apache.commons.io.input.WindowsLineEndingInputStream] At
WindowsLineEndingInputStream.java:[line 113]
AT_STALE_THREAD_WRITE_OF_PRIMITIVE. Thanks to Gary Gregory.
o Fix SpotBugs [ERROR] Medium: Shared primitive variable
&quot;atSlashLf&quot; in one thread may not yield the value of the most
recent write from another thread
[org.apache.commons.io.input.UnixLineEndingInputStream] At
UnixLineEndingInputStream.java:[line 75]
AT_STALE_THREAD_WRITE_OF_PRIMITIVE. Thanks to Gary Gregory.
o Fix SpotBugs [ERROR] Medium: Shared primitive variable
&quot;atEos&quot; in one thread may not yield the value of the most
recent write from another thread
[org.apache.commons.io.input.UnixLineEndingInputStream] At
UnixLineEndingInputStream.java:[line 120]
AT_STALE_THREAD_WRITE_OF_PRIMITIVE. Thanks to Gary Gregory.
o Fix SpotBugs [ERROR] Medium: Shared primitive variable
&quot;atSlashCr&quot; in one thread may not yield the value of the most
recent write from another thread
[org.apache.commons.io.input.UnixLineEndingInputStream] At
UnixLineEndingInputStream.java:[line 124]
AT_STALE_THREAD_WRITE_OF_PRIMITIVE. Thanks to Gary Gregory.
o Fix SpotBugs [ERROR] Medium: Shared primitive variable
&quot;atSlashLf&quot; in one thread may not yield the value of the most
recent write from another thread
[org.apache.commons.io.input.UnixLineEndingInputStream] At
UnixLineEndingInputStream.java:[line 125]
AT_STALE_THREAD_WRITE_OF_PRIMITIVE. Thanks to Gary Gregory.
o Fix SpotBugs [ERROR] Medium: Shared primitive variable
&quot;closed&quot; in one thread may not yield the value of the most
recent write from another thread
[org.apache.commons.io.input.ProxyInputStream] At
ProxyInputStream.java:[line 233] AT_STALE_THREAD_WRITE_OF_PRIMITIVE.
Thanks to Gary Gregory.
o Fix SpotBugs [ERROR] Medium: Shared primitive variable
&quot;propagateClose&quot; in one thread may not yield the value of the
most recent write from another thread
[org.apache.commons.io.input.BoundedInputStream] At
BoundedInputStream.java:[line 555] AT_STALE_THREAD_WRITE_OF_PRIMITIVE.
Thanks to Gary Gregory.
o QueueInputStream reads all but the first byte without waiting. <a
href="https://redirect.github.com/apache/commons-io/issues/748">#748</a>.
Thanks to maxxedev, Piotr P. Karwasz, Gary Gregory.
o          Javadoc fixes and improvements. Thanks to Gary Gregory.
o Avoid NPE in
org.apache.commons.io.filefilter.WildcardFilter.accept(File). Thanks to
Gary Gregory.
o IO-874: FileUtils.forceDelete can delete a broken symlink again <a
href="https://redirect.github.com/apache/commons-io/issues/756">#756</a>.
Thanks to Andy Russell, Joerg Budischewski.
o Fix infinite loop in AbstractByteArrayOutputStream. <a
href="https://redirect.github.com/apache/commons-io/issues/758">#758</a>.
Thanks to Alex Benusovich.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/apache/commons-io/commit/c224bce839494ed651e5eba320b27c73ce8d804e"><code>c224bce</code></a>
Prepare for the release candidate 2.20.0 RC1</li>
<li><a
href="https://github.com/apache/commons-io/commit/8981a5c9664574003f5d7620cf5133325161e543"><code>8981a5c</code></a>
Remove workaround for</li>
<li><a
href="https://github.com/apache/commons-io/commit/4ef481f14220c19f6114a3f793df2202bb1336a6"><code>4ef481f</code></a>
Prepare for the next release candidate</li>
<li><a
href="https://github.com/apache/commons-io/commit/d23228f4a94bd070b0505e5a528da1413915c8a4"><code>d23228f</code></a>
Merge branch 'master' of <a
href="https://github.com/apache/commons-io.git">https://github.com/apache/commons-io.git</a></li>
<li><a
href="https://github.com/apache/commons-io/commit/5d2737ffe489b91c4af7ccddfeda93d860750729"><code>5d2737f</code></a>
Add <a
href="https://github.com/SuppressWarnings"><code>@​SuppressWarnings</code></a></li>
<li><a
href="https://github.com/apache/commons-io/commit/e5c80d6eff29b9a3b2b917356345d90237e84e57"><code>e5c80d6</code></a>
Bump org.apache.commons:commons-lang3 from 3.17.0 to 3.18.0 <a
href="https://redirect.github.com/apache/commons-io/issues/761">#761</a></li>
<li><a
href="https://github.com/apache/commons-io/commit/2017ac063c1cc284dc855265a15a4e2dfdc653e4"><code>2017ac0</code></a>
Bump org.apache.commons:commons-lang3 from 3.17.0 to 3.18.0 (<a
href="https://redirect.github.com/apache/commons-io/issues/761">#761</a>)</li>
<li><a
href="https://github.com/apache/commons-io/commit/07ce798898b6c6ca639e6ad0e2beecf55cf00d7a"><code>07ce798</code></a>
Javadoc</li>
<li><a
href="https://github.com/apache/commons-io/commit/a828efa09f5b32f80485c2302caf78b8ee3c857c"><code>a828efa</code></a>
Add ciManagement element to POM</li>
<li><a
href="https://github.com/apache/commons-io/commit/46bd1c2955a29d676bfbc3fea6cce84918ba6ac5"><code>46bd1c2</code></a>
Javadoc</li>
<li>Additional commits viewable in <a
href="https://github.com/apache/commons-io/compare/rel/commons-io-2.19.0...rel/commons-io-2.20.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=commons-io:commons-io&package-manager=gradle&previous-version=2.19.0&new-version=2.20.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-07-22 10:09:26 +01:00
Anthony Stirling 088466b742 Merge pull request #4016 from Stirling-Tools/V2-rebase
V2 rebase
2025-07-21 20:11:54 +01:00
Anthony Stirling fd072cccba Update build.yml 2025-07-21 19:19:41 +01:00
Anthony Stirling 67d3f28f53 Update build.yml 2025-07-21 18:52:45 +01:00
Anthony Stirling cdb3ffff78 Update build.yml 2025-07-21 16:45:26 +01:00
Anthony Stirling 9adb02fcfb Merge remote-tracking branch 'origin/main' into V2 2025-07-21 15:37:59 +01:00
Anthony Stirling 13bf8210fc Fix endpoint mapping (#3999)
# 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-07-21 14:39:04 +01:00
ConnorYohandConnor Yoh 048277483b Fix search scroll bar issue on navbar (#3998)
Limit mobile drop downs to 1/3 screen height

Closes #3996 

### UI Changes (

<img width="1541" height="1251"
alt="{5866A763-EE40-4E8B-B186-8FD28927F053}"
src="https://github.com/user-attachments/assets/deb1ca76-54df-4cb5-9662-471c914a9f8e"
/>
<img width="546" height="1008"
alt="{9395DF85-6FE0-4359-AA81-A657AC17DF15}"
src="https://github.com/user-attachments/assets/b67ce1c0-5736-4efe-9927-eedf1892e5f9"
/>

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-07-21 13:06:21 +01:00
ConnorYohandConnor Yoh 73e4c70e13 Improved scaling of navbar for page-view and redact (#3978)
# 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.

---------

Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-07-21 11:54:59 +01:00
stirlingbot[bot] 361151e9a7 🤖 format everything with pre-commit by stirlingbot (#3995)
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-07-21 11:29:04 +01:00
Anthony Stirling c553c61376 Disable cookie banner when analytics disabled (#3989)
## Summary
- gate cookie consent resources behind the analytics flag
- hide the cookie preferences link in the footer when analytics are
disabled

Generated with AI.

## Testing
- `./gradlew build`

------
https://chatgpt.com/codex/tasks/task_b_687c9568b2a8832891ffebddb65a9480
2025-07-21 10:25:32 +01:00
LudyandCopilot 04ba3cebab fix: correct paths for python scripts and implement classpath extraction (#3984)
# Description of Changes

- **What was changed**  
- Relocated `png_to_webp.py` and `split_photos.py` from `scripts/` to
`app/core/src/main/resources/static/python/`.
- Updated `.github/labeler-config-srvaroa.yml` and
`.pre-commit-config.yaml` to include the new script directory in their
file-matching patterns.
- Added `GeneralUtils.extractScript(String scriptName)` to load Python
scripts from the classpath (`static/python/`), extract them into a
temporary directory at runtime, and return the filesystem path.

- **Why the change was made**  
- To fix the Internal Server Error caused by missing script files at
their old locations.
- Ensure the Python helper scripts are packaged inside the JAR/WAR and
reliably accessible when the application runs.
  - Only local installations were affected

---

## 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.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-20 22:21:12 +01:00
stirlingbot[bot] 7b61bbaced 🌐 Sync Translations + Update README Progress Table (#3994)
### 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-07-20 22:00:51 +01:00
AngelandLudy be7780d7f8 Update messages_ru_RU.properties (#3967)
Updated Russian translation

Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
2025-07-20 21:41:27 +01:00
Ludy d1b9cfd1a4 fix(ci): disable Gradle caching in GitHub workflows to resolve cache conflicts (#3986)
# Description of Changes

- **What was changed**  
In all affected GitHub Actions workflow files under
`.github/workflows/`, the `cache: gradle` setting was replaced with
`cache: false # Disable Gradle caching for this job`.
- **Why the change was made**  
Gradle’s cache was causing intermittent build conflicts; disabling it
ensures each job starts from a clean state and avoids cache corruption.

#3980

---

## 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-07-20 21:37:04 +01:00
ConnorYohandConnor Yoh 64d8ef4a39 Update CODEOWNERS (#3981)
Co-authored-by: Connor Yoh <connor@stirlingpdf.com>
2025-07-18 18:25:50 +01:00
pixeebotstirling[bot] d79d179d80 (Snyk) Fixed finding: "java/PT" (#3976)
**Pixee Fix ID:**
[fb5fe72b-5b22-4654-a733-20930cb4f96a](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/fb5fe72b-5b22-4654-a733-20930cb4f96a)

<details>
  <summary>Confidence: <b>HIGH</b></summary>

Fix confidence is a rating derived from an internal benchmark and
includes High, Medium, and Low confidence fixes. It comprises three
weighted scores reflecting the safety, effectiveness and cleanliness of
Pixee's code changes within a fix. [View Details in
Pixee.](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/fb5fe72b-5b22-4654-a733-20930cb4f96a)
</details>

---



## Remediation

This change fixes "java/PT" (id = java/PT) identified by Snyk.

## Details

Path traversal is a security vulnerability that occurs when an attacker
is able to access directories and files stored outside the intended
directory. It bypasses security mechanisms by manipulating variables
that reference files with `../` sequences. The fix involved adding
validation for `pdfFile` and `watermarkImage` to check for directory
traversal sequences, thereby preventing SecurityException occurrences.

Co-authored-by: pixeebotstirling[bot] <221352955+pixeebotstirling[bot]@users.noreply.github.com>
2025-07-17 17:18:27 +01:00
pixeebotstirling[bot] d15a275406 (Snyk) Fixed finding: "java/PT" (#3975)
**Pixee Fix ID:**
[203062ab-1b9b-42b8-be64-1358106dccab](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/203062ab-1b9b-42b8-be64-1358106dccab)

<details>
  <summary>Confidence: <b>HIGH</b></summary>

Fix confidence is a rating derived from an internal benchmark and
includes High, Medium, and Low confidence fixes. It comprises three
weighted scores reflecting the safety, effectiveness and cleanliness of
Pixee's code changes within a fix. [View Details in
Pixee.](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/203062ab-1b9b-42b8-be64-1358106dccab)
</details>

---



## Remediation

This change fixes "java/PT" (id = java/PT) identified by Snyk.

## Details

Path Traversal is a security vulnerability that allows attackers to gain
unauthorized access to files and directories outside the permitted
access path by manipulating file paths. The fix involves adding
validation to detect potential directory traversal attempts by
normalizing the file path and checking if it begins with '..', thereby
preventing malicious manipulation.

Co-authored-by: pixeebotstirling[bot] <221352955+pixeebotstirling[bot]@users.noreply.github.com>
2025-07-17 17:17:55 +01:00
pixeebotstirling[bot] ed894f021b (Snyk) Fixed finding: "java/PT" (#3974)
**Pixee Fix ID:**
[dab7f6f1-da39-4654-a537-2de8eee936db](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/dab7f6f1-da39-4654-a537-2de8eee936db)

<details>
  <summary>Confidence: <b>HIGH</b></summary>

Fix confidence is a rating derived from an internal benchmark and
includes High, Medium, and Low confidence fixes. It comprises three
weighted scores reflecting the safety, effectiveness and cleanliness of
Pixee's code changes within a fix. [View Details in
Pixee.](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/dab7f6f1-da39-4654-a537-2de8eee936db)
</details>

---



## Remediation

This change fixes "java/PT" (id = java/PT) identified by Snyk.

## Details

Path Traversal is a security vulnerability that allows attackers to
access files and directories stored outside the web root folder. The
impact can include unauthorized access to sensitive files. The fix
involved adding validation checks on filenames to ensure they do not
contain suspicious patterns like '..' or '/' which are indicative of
path traversal attempts.

Co-authored-by: pixeebotstirling[bot] <221352955+pixeebotstirling[bot]@users.noreply.github.com>
2025-07-17 17:17:11 +01:00
pixeebotstirling[bot] 76d150289e (Snyk) Fixed finding: "java/Ssrf" (#3973)
**Pixee Fix ID:**
[54568072-e1ef-4428-9da3-46b9197f6dcd](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/54568072-e1ef-4428-9da3-46b9197f6dcd)

<details>
  <summary>Confidence: <b>HIGH</b></summary>

Fix confidence is a rating derived from an internal benchmark and
includes High, Medium, and Low confidence fixes. It comprises three
weighted scores reflecting the safety, effectiveness and cleanliness of
Pixee's code changes within a fix. [View Details in
Pixee.](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/54568072-e1ef-4428-9da3-46b9197f6dcd)
</details>

---



## Remediation

This change fixes "java/Ssrf" (id = java/Ssrf) identified by Snyk.

## Details

Server-Side Request Forgery (SSRF) can allow attackers to make
unauthorized requests from the server, potentially accessing sensitive
internal systems. The fix involved adding a validation check to ensure
that the 'operation' parameter matches a safe regex pattern, thereby
preventing such vulnerabilities.

Co-authored-by: pixeebotstirling[bot] <221352955+pixeebotstirling[bot]@users.noreply.github.com>
2025-07-17 17:16:50 +01:00
pixeebotstirling[bot] 526071059e (Snyk) Fixed finding: "java/PT" (#3972)
**Pixee Fix ID:**
[8be62d8f-950d-4780-bc08-a8c04d176806](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/8be62d8f-950d-4780-bc08-a8c04d176806)

<details>
  <summary>Confidence: <b>HIGH</b></summary>

Fix confidence is a rating derived from an internal benchmark and
includes High, Medium, and Low confidence fixes. It comprises three
weighted scores reflecting the safety, effectiveness and cleanliness of
Pixee's code changes within a fix. [View Details in
Pixee.](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/8be62d8f-950d-4780-bc08-a8c04d176806)
</details>

---



## Remediation

This change fixes "java/PT" (id = java/PT) identified by Snyk.

## Details

Path Traversal vulnerabilities allow attackers to manipulate paths to
access files and directories that are outside of the intended scope. The
fix involves adding an import for java.io.File to handle file paths more
securely.

Co-authored-by: pixeebotstirling[bot] <221352955+pixeebotstirling[bot]@users.noreply.github.com>
2025-07-17 17:16:09 +01:00
pixeebotstirling[bot] de43038fd9 (Snyk) Fixed finding: "java/PT" (#3971)
**Pixee Fix ID:**
[baa2e86a-2e2f-4c8e-99e0-bc99ce846b94](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/baa2e86a-2e2f-4c8e-99e0-bc99ce846b94)

<details>
  <summary>Confidence: <b>HIGH</b></summary>

Fix confidence is a rating derived from an internal benchmark and
includes High, Medium, and Low confidence fixes. It comprises three
weighted scores reflecting the safety, effectiveness and cleanliness of
Pixee's code changes within a fix. [View Details in
Pixee.](https://stirlingpdf.getpixee.com/analysis/3c9d2b94-57c2-4525-9776-c5cd149902c4/fix/baa2e86a-2e2f-4c8e-99e0-bc99ce846b94)
</details>

---



## Remediation

This change fixes "java/PT" (id = java/PT) identified by Snyk.

## Details

Path Traversal is a security vulnerability that enables attackers to
gain unauthorized access to files and directories stored outside the web
root folder. To address this, a fix was made by adding
java.nio.file.Paths import for path validation, ensuring that paths are
properly sanitized and validated.

Co-authored-by: pixeebotstirling[bot] <221352955+pixeebotstirling[bot]@users.noreply.github.com>
2025-07-17 17:15:39 +01:00
Ludy fc9551a332 ci: enhance GitHub Actions workflows with Gradle setup, caching improvements, and Docker image testing (#3956)
# Description of Changes

- **What was changed**:  
- Added a new path filter configuration file at
`.github/config/.files.yaml` to centralize filter groups (`build`,
`app`, `openapi`, `project`).
  - Updated `.github/workflows/build.yml` to:  
- Rename the workflow to “Build and Test Workflow” and add a manual
`workflow_dispatch` trigger.
- Integrate the path-filter step and conditionally run jobs
(`check-generateOpenApiDocs`, `check-licence`, `docker-compose-tests`)
based on changed files.
    - Standardize Gradle setup to version 8.14.  
- Introduce a new `test-build-docker-images` job that builds Docker
images for each `Dockerfile*` in PRs.
- Updated `.github/workflows/pre_commit.yml` to cache pre-commit
dependencies via `cache-dependency-path:
./.github/scripts/requirements_pre_commit.txt`.
- Updated `.github/workflows/testdriver.yml` to add dedicated Gradle
(`gradle-version: 8.14`) and Node/npm setup steps with caching.

- **Why the change was made**:  
To optimize CI performance by only running relevant jobs when specific
files change, improve maintainability through a single source of truth
for path filters, enable manual workflow dispatch, ensure consistent
environments (Gradle, Node), and speed up runs with better caching.


---

## 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-07-16 17:17:11 +01:00
dependabot[bot] b510df9951 build(deps): bump org.snakeyaml:snakeyaml-engine from 2.9 to 2.10 (#3948)
Bumps
[org.snakeyaml:snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine)
from 2.9 to 2.10.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/61e3f77df22d165ca210f9ef61abbe4c7a764b10"><code>61e3f77</code></a>
Issue 56: improve code</li>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/ab996bb428a3769b7118ecc5a4035d64514464b8"><code>ab996bb</code></a>
Issue 56: refactor rename variable</li>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/7b316bbad0fdb51b9027652c25963d062cbebe65"><code>7b316bb</code></a>
Issue 56: refactor rename variable</li>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/c46d5f741720b5ba1152e0d7de2c21e6c0ee42c8"><code>c46d5f7</code></a>
Issue 56: remove unused merge code in StandardConstructor</li>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/553748c968e1b22969f1411e5e061ed3dfb96403"><code>553748c</code></a>
Fix issue 55 only for JSON</li>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/b484094bfcea73d167a2d284ec8289e4ce3d316f"><code>b484094</code></a>
Clarify when to expect NoSuchElementException</li>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/e6b30e3b8d385e7e555d919b18dbc6ad571bd989"><code>e6b30e3</code></a>
Wrap IOException in UncheckedIOException by default</li>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/5d0fa73483309d837e43d8da1ba25a9446ceafd8"><code>5d0fa73</code></a>
Update changes</li>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/3e19067a981c376eb146dedf8e74810d6d539cf8"><code>3e19067</code></a>
Update changes</li>
<li><a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/commits/4de1481f10c1cf90ff5b33abe1cf31709f3bdf62"><code>4de1481</code></a>
Merge branch 'master' into issue-55-json</li>
<li>Additional commits viewable in <a
href="https://bitbucket.org/snakeyaml/snakeyaml-engine/branches/compare/snakeyaml-engine-2.10..snakeyaml-engine-2.9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.snakeyaml:snakeyaml-engine&package-manager=gradle&previous-version=2.9&new-version=2.10)](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-07-16 14:53:10 +01:00
stirlingbot[bot] 71394a2f85 🌐 Sync Translations + Update README Progress Table (#3962)
### 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-07-16 14:52:27 +01:00
Anthony Stirling cc7f163085 fake scan to scanner effect (#3954)
# 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-07-16 14:33:04 +01:00
dependabot[bot] 638d9ec43c build(deps): bump io.micrometer:micrometer-core from 1.15.1 to 1.15.2 (#3947)
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.micrometer:micrometer-core&package-manager=gradle&previous-version=1.15.1&new-version=1.15.2)](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-07-16 12:42:05 +01:00
stirlingbot[bot] 5923cb38d3 🌐 Sync Translations + Update README Progress Table (#3960)
### 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-07-16 10:34:51 +01:00
659 changed files with 43172 additions and 9941 deletions
+7 -2
View File
@@ -6,8 +6,13 @@
"Bash(./gradlew:*)",
"Bash(grep:*)",
"Bash(cat:*)",
"Bash(find:*)"
"Bash(find:*)",
"Bash(npm test)",
"Bash(npm test:*)",
"Bash(ls:*)",
"Bash(npx tsc:*)",
"Bash(sed:*)"
],
"deny": []
}
}
}
+12 -1
View File
@@ -49,7 +49,7 @@
"java.configuration.updateBuildConfiguration": "interactive",
"java.format.enabled": true,
"java.format.settings.profile": "GoogleStyle",
"java.format.settings.google.version": "1.26.0",
"java.format.settings.google.version": "1.28.0",
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
"java.saveActions.cleanup": true,
"java.cleanup.actions": [
@@ -79,9 +79,17 @@
".venv*/",
".vscode/",
"bin/",
"app/core/bin/",
"app/common/bin/",
"app/proprietary/bin/",
"build/",
"app/core/build/",
"app/common/build/",
"app/proprietary/build/",
"configs/",
"app/core/configs/",
"customFiles/",
"app/core/customFiles/",
"docs/",
"exampleYmlFiles",
"gradle/",
@@ -93,6 +101,9 @@
".git-blame-ignore-revs",
".gitattributes",
".gitignore",
"app/core/.gitignore",
"app/common/.gitignore",
"app/proprietary/.gitignore",
".pre-commit-config.yaml"
],
"java.signatureHelp.enabled": true,
+2 -8
View File
@@ -24,25 +24,19 @@ indent_size = 2
insert_final_newline = false
trim_trailing_whitespace = false
[*.js]
[{*.js,*.jsx,*.ts,*.tsx}]
indent_size = 2
[*.css]
# CSS files typically use an indent size of 2 spaces for better readability and alignment with community standards.
indent_size = 2
[*.yaml]
[*.{yml,yaml}]
# YAML files use an indent size of 2 spaces to maintain consistency with common YAML formatting practices.
indent_size = 2
insert_final_newline = false
trim_trailing_whitespace = false
[*.yml]
# YML files follow the same conventions as YAML files, using an indent size of 2 spaces.
indent_size = 2
insert_final_newline = false
trim_trailing_whitespace = false
[*.json]
# JSON files use an indent size of 2 spaces, which is the standard for JSON formatting.
indent_size = 2
+21 -2
View File
@@ -1,2 +1,21 @@
# All PRs to V1 must be approved by Frooodle
* @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh
# All PRs must be approved by Frooodle or Ludy87
* @Frooodle @Ludy87 @jbrunton96 @ConnorYoh
# Backend
/app/** @DarioGii @Frooodle @Ludy87 @jbrunton96 @ConnorYoh
#V1 frontend
/app/core/src/main/resources/static/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle @Ludy87
/app/core/src/main/resources/templates/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle @Ludy87
#V2 frontend
/frontend/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle
#V2 docker
/docker/backend/** @Frooodle @Ludy87 @DarioGii @Ludy87
/docker/frontend/** @reecebrowne @ConnorYoh @EthanHealy01 @jbrunton96 @Frooodle @Ludy87
/docker/compose/** @reecebrowne @ConnorYoh @EthanHealy01 @DarioGii @jbrunton96 @Frooodle @Ludy87
#GHA (All users)
/.github/** @reecebrowne @ConnorYoh @EthanHealy01 @DarioGii @jbrunton96 @Frooodle @Ludy87
+31
View File
@@ -0,0 +1,31 @@
build: &build
- build.gradle
- app/(common|core|proprietary)/build.gradle
app: &app
- app/(common|core|proprietary)/src/main/java/**
openapi: &openapi
- build.gradle
- app/(common|core|proprietary)/build.gradle
- app/(common|core|proprietary)/src/main/java/**
project: &project
- app/(common|core|proprietary)/src/(main|test)/java/**
- app/(common|core|proprietary)/build.gradle
- 'app/(common|core|proprietary)/src/(main|test)/resources/**/!(messages_*.properties|*.md)*'
- exampleYmlFiles/**
- gradle/**
- libs/**
- testing/**
- build.gradle
- Dockerfile
- Dockerfile.fat
- Dockerfile.ultra-lite
- gradle.properties
- gradlew
- gradlew.bat
- launch4jConfig.xml
- settings.gradle
- frontend/**
- docker/**
+2 -1
View File
@@ -7,6 +7,7 @@
"sbplat",
"reecebrowne",
"DarioGii",
"ConnorYoh"
"ConnorYoh",
"EthanHealy01"
]
}
+10 -5
View File
@@ -46,6 +46,9 @@ labels:
- label: 'API'
title: '.*openapi.*|.*swagger.*|.*api.*'
- label: 'v2'
base-branch: 'V2'
- label: 'Translation'
files:
- 'app/core/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}.properties'
@@ -62,6 +65,7 @@ labels:
- 'app/core/src/main/java/stirling/software/SPDF/controller/web/.*'
- 'app/core/src/main/java/stirling/software/SPDF/UI/.*'
- 'app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/.*'
- 'frontend/**'
- label: 'Java'
files:
@@ -76,8 +80,9 @@ labels:
- 'app/core/src/main/resources/settings.yml.template'
- 'app/core/src/main/resources/application.properties'
- 'app/core/src/main/resources/banner.txt'
- 'scripts/png_to_webp.py'
- 'split_photos.py'
- 'app/core/src/main/resources/static/python/png_to_webp.py'
- 'app/core/src/main/resources/static/python/split_photos.py'
- 'app/core/src/main/resources/static/pipeline/defaultWebUIConfigs/**'
- 'application.properties'
- label: 'Security'
@@ -95,8 +100,8 @@ labels:
- 'app/core/src/main/java/stirling/software/SPDF/model/api/.*'
- 'app/core/src/main/java/stirling/software/SPDF/service/ApiDocService.java'
- 'app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/.*'
- 'scripts/png_to_webp.py'
- 'split_photos.py'
- 'app/core/src/main/resources/static/python/png_to_webp.py'
- 'app/core/src/main/resources/static/python/split_photos.py'
- '.github/workflows/swagger.yml'
- label: 'Documentation'
@@ -119,6 +124,7 @@ labels:
- 'scripts/installFonts.sh'
- 'test.sh'
- 'test2.sh'
- 'docker/**'
- label: 'Devtools'
files:
@@ -130,7 +136,6 @@ labels:
- '.github/workflows/pre_commit.yml'
- 'devGuide/.*'
- 'devTools/.*'
- 'devTools/.*'
- label: 'Test'
files:
+6
View File
@@ -42,6 +42,7 @@
- name: "Front End"
color: "BBD2F1"
description: "Issues or pull requests related to front-end development"
from_name: "frontend"
- name: "github-actions"
description: "Pull requests that update GitHub Actions code"
color: "999999"
@@ -77,10 +78,12 @@
- name: "Translation"
color: "9FABF9"
from_name: "translation"
description: "Issues or pull requests related to translation"
- name: "upstream"
color: "DEDEDE"
- name: "v2"
color: "FFFF00"
description: "Issues or pull requests related to the v2 branch"
- name: "wontfix"
description: "This will not be worked on"
color: "FFFFFF"
@@ -178,3 +181,6 @@
- name: "pr-deployed"
color: "00FF00"
description: "Pull request has been deployed to a test environment"
- name: "codex"
color: "ededed"
description: "chatgpt AI generated code"
+10 -5
View File
@@ -48,6 +48,7 @@ jobs:
"DarioGii"
"ConnorYoh"
"EthanHealy01"
"jbrunton96"
)
# Check if author is in the authorized list
@@ -317,6 +318,8 @@ jobs:
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
SWAGGER_SERVER_URL: "https://${V2_PORT}.ssl.stirlingpdf.cloud"
baseUrl: "https://${V2_PORT}.ssl.stirlingpdf.cloud"
restart: on-failure:5
stirling-pdf-v2-frontend:
@@ -350,10 +353,10 @@ jobs:
docker-compose up -d
# Clean up unused Docker resources to save space
docker system prune -af --volumes
docker system prune -af --volumes || true
# Clean up old backend/frontend images (older than 2 weeks)
docker image prune -af --filter "until=336h" --filter "label!=keep=true"
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
ENDSSH
# Set port for output
@@ -385,10 +388,12 @@ jobs:
}
const deploymentUrl = `http://${{ secrets.VPS_HOST }}:${v2Port}`;
const httpsUrl = `https://${v2Port}.ssl.stirlingpdf.cloud`;
const commentBody = `## 🚀 V2 Auto-Deployment Complete!\n\n` +
`Your V2 PR with the new frontend/backend split architecture has been deployed!\n\n` +
`🔗 **V2 Test URL:** [${deploymentUrl}](${deploymentUrl})\n\n` +
`🔗 **Direct Test URL (non-SSL)** [${deploymentUrl}](${deploymentUrl})\n\n` +
`🔐 **Secure HTTPS URL**: [${httpsUrl}](${httpsUrl})\n\n` +
`_This deployment will be automatically cleaned up when the PR is closed._\n\n` +
`🔄 **Auto-deployed** because PR title or branch name contains V2/version2/React keywords.`;
@@ -486,7 +491,7 @@ jobs:
fi
# Clean up old unused images (older than 2 weeks) but keep recent ones for reuse
docker image prune -af --filter "until=336h" --filter "label!=keep=true"
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
# Note: We don't remove the commit-based images since they can be reused across PRs
# Only remove PR-specific containers and directories
@@ -29,6 +29,7 @@ jobs:
github.event.comment.user.login == 'reecebrowne' ||
github.event.comment.user.login == 'DarioGii' ||
github.event.comment.user.login == 'EthanHealy01' ||
github.event.comment.user.login == 'jbrunton96' ||
github.event.comment.user.login == 'ConnorYoh'
)
outputs:
@@ -41,12 +42,12 @@ jobs:
enable_enterprise: ${{ steps.check-pro-flag.outputs.enable_enterprise }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
@@ -152,12 +153,12 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
@@ -169,7 +170,7 @@ jobs:
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Checkout PR
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
repository: ${{ needs.check-comment.outputs.pr_repository }}
ref: ${{ needs.check-comment.outputs.pr_ref }}
@@ -196,7 +197,7 @@ jobs:
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
+2 -2
View File
@@ -21,12 +21,12 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
+3 -3
View File
@@ -19,11 +19,11 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
fetch-depth: 0
@@ -87,7 +87,7 @@ jobs:
- name: AI PR Title Analysis
if: steps.actor.outputs.is_repo_dev == 'true'
id: ai-title-analysis
uses: actions/ai-inference@d645f067d89ee1d5d736a5990e327e504d1c5a4a # v1.1.0
uses: actions/ai-inference@b81b2afb8390ee6839b494a404766bef6493c7d9 # v1.2.8
with:
model: openai/gpt-4o
system-prompt-file: ".github/config/system-prompt.txt"
+2 -2
View File
@@ -13,11 +13,11 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Setup GitHub App Bot
id: setup-bot
+144 -74
View File
@@ -1,50 +1,76 @@
name: Build repo
name: Build and Test Workflow
on:
push:
branches: ["main", "V2", "V2-gha"]
pull_request:
branches: ["main", "V2", "V2-gha"]
workflow_dispatch:
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
files-changed:
name: detect what files changed
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
build: ${{ steps.changes.outputs.build }}
app: ${{ steps.changes.outputs.app }}
project: ${{ steps.changes.outputs.project }}
openapi: ${{ steps.changes.outputs.openapi }}
steps:
- uses: actions/checkout@v4.3.0
- name: Check for file changes
uses: dorny/paths-filter@v3.0.2
id: changes
with:
filters: .github/config/.files.yaml
build:
runs-on: ubuntu-latest
permissions:
actions: read
security-events: write
strategy:
fail-fast: false
matrix:
jdk-version: [17, 21]
spring-security: [true, false]
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@v4.3.0
- name: Set up JDK ${{ matrix.jdk-version }}
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
uses: actions/setup-java@v4.7.1
with:
java-version: ${{ matrix.jdk-version }}
distribution: "temurin"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4.4.2
with:
gradle-version: 8.14
- name: Build with Gradle and spring security ${{ matrix.spring-security }}
run: ./gradlew clean build
run: ./gradlew clean build -PnoSpotless
env:
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.spring-security }}
- name: Check Test Reports Exist
id: check-reports
if: always()
run: |
declare -a dirs=(
@@ -55,61 +81,51 @@ jobs:
"app/proprietary/build/reports/tests/"
"app/proprietary/build/test-results/"
)
missing_reports=()
for dir in "${dirs[@]}"; do
if [ ! -d "$dir" ]; then
missing_reports+=("$dir")
echo "Missing $dir"
exit 1
fi
done
if [ ${#missing_reports[@]} -gt 0 ]; then
echo "ERROR: The following required test report directories are missing:"
printf '%s\n' "${missing_reports[@]}"
exit 1
fi
echo "All required test report directories are present"
- name: Upload Test Reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4.6.2
with:
name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }}
path: |
app/core/build/reports/tests/
app/core/build/test-results/
app/core/build/reports/problems/
app/common/build/reports/tests/
app/common/build/test-results/
app/common/build/reports/problems/
app/proprietary/build/reports/tests/
app/proprietary/build/test-results/
app/proprietary/build/reports/problems/
app/**/build/reports/tests/
app/**/build/test-results/
app/**/build/reports/problems/
build/reports/problems/
retention-days: 3
if-no-files-found: warn
check-generateOpenApiDocs:
if: needs.files-changed.outputs.openapi == 'true'
needs: [files-changed]
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Checkout repository
uses: actions/checkout@v4.3.0
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
uses: actions/setup-java@v4.7.1
with:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- uses: gradle/actions/setup-gradle@v4.4.2
- name: Generate OpenAPI documentation
run: ./gradlew :stirling-pdf:generateOpenApiDocs
env:
DISABLE_ADDITIONAL_FEATURES: true
- name: Upload OpenAPI Documentation
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4.6.2
with:
name: openapi-docs
path: ./SwaggerDoc.json
@@ -118,72 +134,59 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@v2.12.2
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@v4.2.2
- name: Set up Node.js
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
uses: actions/setup-node@v4.1.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
run: |
cd frontend
npm ci
run: cd frontend && npm ci
- name: Build frontend
run: |
cd frontend
npm run build
- name: Run frontend tests (if available)
run: |
cd frontend
npm test --passWithNoTests --watchAll=false || true
run: cd frontend && npm run build
- name: Run frontend tests
run: cd frontend && npm run test -- --run
- name: Upload frontend build artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4.6.2
with:
name: frontend-build
path: frontend/dist/
retention-days: 3
check-licence:
if: needs.files-changed.outputs.build == 'true'
needs: [files-changed, build]
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@v4.3.0
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
uses: actions/setup-java@v4.7.1
with:
java-version: "17"
distribution: "adopt"
distribution: "temurin"
- name: check the licenses for compatibility
run: ./gradlew clean checkLicense
- name: FAILED - check the licenses for compatibility
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4.6.2
with:
name: dependencies-without-allowed-license.json
path: |
build/reports/dependency-license/dependencies-without-allowed-license.json
path: build/reports/dependency-license/dependencies-without-allowed-license.json
retention-days: 3
docker-compose-tests:
if: needs.files-changed.outputs.project == 'true'
needs: files-changed
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
# (github.event_name == 'pull_request' &&
# contains(github.event.pull_request.labels.*.name, 'licenses') == false &&
@@ -202,18 +205,18 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up Java 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
java-version: "17"
distribution: "adopt"
distribution: "temurin"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
@@ -228,6 +231,7 @@ jobs:
with:
python-version: "3.12"
cache: 'pip' # caching pip dependencies
cache-dependency-path: ./testing/cucumber/requirements.txt
- name: Pip requirements
run: |
@@ -239,3 +243,69 @@ jobs:
chmod +x ./testing/test.sh
chmod +x ./testing/test_disabledEndpoints.sh
./testing/test.sh
test-build-docker-images:
if: github.event_name == 'pull_request' && needs.files-changed.outputs.project == 'true'
needs: [files-changed, build, check-generateOpenApiDocs, check-licence]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
docker-rev: ["Dockerfile", "Dockerfile.ultra-lite", "Dockerfile.fat"]
steps:
- name: Harden Runner
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout Repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
java-version: "17"
distribution: "temurin"
- name: Set up Gradle
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
with:
gradle-version: 8.14
- name: Build application
run: ./gradlew clean build
env:
DISABLE_ADDITIONAL_FEATURES: true
STIRLING_PDF_DESKTOP_UI: false
- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Build ${{ matrix.docker-rev }}
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./docker/backend/${{ matrix.docker-rev }}
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64/v8
provenance: true
sbom: true
- name: Upload Reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: reports-docker-${{ matrix.docker-rev }}
path: |
build/reports/tests/
build/test-results/
build/reports/problems/
retention-days: 3
if-no-files-found: warn
+16 -4
View File
@@ -6,6 +6,18 @@ on:
paths:
- "app/core/src/main/resources/messages_*.properties"
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
contents: read # Allow read access to repository content
@@ -18,12 +30,12 @@ jobs:
pull-requests: write # Allow writing to pull requests
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout main branch first
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Setup GitHub App Bot
id: setup-bot
@@ -115,7 +127,7 @@ jobs:
// Filter for relevant files based on the PR changes
const changedFiles = files
.filter(file =>
.filter(file =>
file.status !== "removed" &&
/^app\/core\/src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file.filename)
)
@@ -277,4 +289,4 @@ jobs:
rm -rf pr-branch
rm -f pr-branch-messages_en_GB.properties main-branch-messages_en_GB.properties changed_files.txt result.txt
echo "Cleanup complete."
continue-on-error: true # Ensure cleanup runs even if previous steps fail
continue-on-error: true # Ensure cleanup runs even if previous steps fail
+2 -2
View File
@@ -17,11 +17,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: "Checkout Repository"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: "Dependency Review"
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
+189
View File
@@ -0,0 +1,189 @@
name: Auto V2 Deploy on Push
on:
push:
branches:
- V2
- deploy-on-v2-commit
permissions:
contents: read
jobs:
deploy-v2-on-push:
runs-on: ubuntu-latest
concurrency:
group: deploy-v2-push-V2
cancel-in-progress: true
steps:
- name: Harden Runner
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Get commit hashes for frontend and backend
id: commit-hashes
run: |
# Get last commit that touched the frontend folder, docker/frontend, or docker/compose
FRONTEND_HASH=$(git log -1 --format="%H" -- frontend/ docker/frontend/ docker/compose/ 2>/dev/null || echo "")
if [ -z "$FRONTEND_HASH" ]; then
FRONTEND_HASH="no-frontend-changes"
fi
# Get last commit that touched backend code, docker/backend, or docker/compose
BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
if [ -z "$BACKEND_HASH" ]; then
BACKEND_HASH="no-backend-changes"
fi
echo "Frontend hash: $FRONTEND_HASH"
echo "Backend hash: $BACKEND_HASH"
echo "frontend_hash=$FRONTEND_HASH" >> $GITHUB_OUTPUT
echo "backend_hash=$BACKEND_HASH" >> $GITHUB_OUTPUT
# Short hashes for tags
if [ "$FRONTEND_HASH" = "no-frontend-changes" ]; then
echo "frontend_short=no-frontend" >> $GITHUB_OUTPUT
else
echo "frontend_short=${FRONTEND_HASH:0:8}" >> $GITHUB_OUTPUT
fi
if [ "$BACKEND_HASH" = "no-backend-changes" ]; then
echo "backend_short=no-backend" >> $GITHUB_OUTPUT
else
echo "backend_short=${BACKEND_HASH:0:8}" >> $GITHUB_OUTPUT
fi
- name: Check if frontend image exists
id: check-frontend
run: |
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Frontend image already exists, skipping build"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Frontend image needs to be built"
fi
- name: Check if backend image exists
id: check-backend
run: |
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Backend image already exists, skipping build"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Backend image needs to be built"
fi
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push frontend image
if: steps.check-frontend.outputs.exists == 'false'
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/frontend/Dockerfile
push: true
tags: |
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-latest
build-args: VERSION_TAG=v2-alpha
platforms: linux/amd64
- name: Build and push backend image
if: steps.check-backend.outputs.exists == 'false'
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/backend/Dockerfile
push: true
tags: |
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-latest
build-args: VERSION_TAG=v2-alpha
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
chmod 600 ../private.key
- name: Deploy to VPS on port 3000
run: |
export UNIQUE_NAME=docker-compose-v2-$GITHUB_RUN_ID.yml
cat > $UNIQUE_NAME << EOF
version: '3.3'
services:
backend:
container_name: stirling-v2-backend
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
ports:
- "13000:8080"
volumes:
- /stirling/V2/data:/usr/share/tessdata:rw
- /stirling/V2/config:/configs:rw
- /stirling/V2/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "true"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-GB
UI_APPNAME: "Stirling-PDF V2"
UI_HOMEDESCRIPTION: "V2 Frontend/Backend Split"
UI_APPNAMENAVBAR: "V2 Deployment"
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
SWAGGER_SERVER_URL: "https://demo.stirlingpdf.cloud"
baseUrl: "https://demo.stirlingpdf.cloud"
restart: on-failure:5
frontend:
container_name: stirling-v2-frontend
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
ports:
- "3000:80"
environment:
VITE_API_BASE_URL: "http://${{ secrets.VPS_HOST }}:13000"
depends_on:
- backend
restart: on-failure:5
EOF
# Copy to remote with unique name
scp -i ../private.key -o StrictHostKeyChecking=no $UNIQUE_NAME ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/$UNIQUE_NAME
# SSH and rename/move atomically to avoid interference
ssh -i ../private.key -o StrictHostKeyChecking=no ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << ENDSSH
mkdir -p /stirling/V2/{data,config,logs}
mv /tmp/$UNIQUE_NAME /stirling/V2/docker-compose.yml
cd /stirling/V2
docker-compose down || true
docker-compose pull
docker-compose up -d
docker system prune -af --volumes || true
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
ENDSSH
- name: Cleanup temporary files
if: always()
run: |
rm -f ../private.key
@@ -126,19 +126,19 @@ jobs:
commentBody = `## ❌ Frontend License Check Failed
The frontend license check has detected compatibility warnings that require review:
The frontend license check has detected compatibility warnings that require review:
${warningDetails}
${warningDetails}
**Action Required:** Please review these licenses to ensure they are acceptable for your use case before merging.
**Action Required:** Please review these licenses to ensure they are acceptable for your use case before merging.
_This check will fail the PR until license issues are resolved._`;
_This check will fail the PR until license issues are resolved._`;
} else {
commentBody = `## ✅ Frontend License Check Passed
All frontend licenses have been validated and no compatibility warnings were detected.
All frontend licenses have been validated and no compatibility warnings were detected.
The frontend license report has been updated successfully.`;
The frontend license report has been updated successfully.`;
}
await github.rest.issues.createComment({
+16 -4
View File
@@ -7,6 +7,18 @@ on:
paths:
- "build.gradle"
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
contents: read
@@ -19,12 +31,12 @@ jobs:
repository-projects: write # Required for enabling automerge
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Check out code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
fetch-depth: 0
@@ -39,10 +51,10 @@ jobs:
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
java-version: "17"
distribution: "adopt"
distribution: "temurin"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
- name: Check licenses for compatibility
run: ./gradlew clean checkLicense
+2 -2
View File
@@ -15,12 +15,12 @@ jobs:
issues: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Check out the repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Run Labeler
uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5.3.0
+15 -15
View File
@@ -21,11 +21,11 @@ jobs:
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up JDK
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
@@ -60,11 +60,11 @@ jobs:
file_suffix: ""
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
@@ -72,7 +72,7 @@ jobs:
java-version: "21"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
with:
gradle-version: 8.14
@@ -110,12 +110,12 @@ jobs:
file_suffix: ""
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Download build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: stirling-${{ matrix.file_suffix }}binaries
@@ -148,11 +148,11 @@ jobs:
contents: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
@@ -160,7 +160,7 @@ jobs:
java-version: "21"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
with:
gradle-version: 8.14
@@ -238,12 +238,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Download build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: ${{ matrix.platform }}binaries
@@ -252,7 +252,7 @@ jobs:
- name: Install Cosign
if: matrix.os == 'windows-latest'
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
- name: Generate key pair
if: matrix.os == 'windows-latest'
@@ -301,12 +301,12 @@ jobs:
contents: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Download signed artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
- name: Display structure of downloaded files
run: ls -R
- name: Upload binaries, attestations and signatures to Release and create GitHub Release
+15 -4
View File
@@ -2,8 +2,9 @@ name: Pre-commit
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 1"
push:
branches:
- main
permissions:
contents: read
@@ -16,12 +17,12 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
fetch-depth: 0
@@ -37,6 +38,7 @@ jobs:
with:
python-version: 3.12
cache: 'pip' # caching pip dependencies
cache-dependency-path: ./.github/scripts/requirements_pre_commit.txt
- name: Run Pre-Commit Hooks
run: |
@@ -45,6 +47,15 @@ jobs:
- run: pre-commit run --all-files -c .pre-commit-config.yaml
continue-on-error: true
- name: Set up JDK
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
java-version: 17
distribution: "temurin"
- name: Build with Gradle
run: ./gradlew clean build
- name: git add
run: |
git add .
+21 -9
View File
@@ -7,6 +7,18 @@ on:
- master
- main
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
contents: read
@@ -18,11 +30,11 @@ jobs:
id-token: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
@@ -30,7 +42,7 @@ jobs:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
with:
gradle-version: 8.14
@@ -42,7 +54,7 @@ jobs:
- name: Install cosign
if: github.ref == 'refs/heads/master'
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
with:
cosign-release: "v2.4.1"
@@ -55,13 +67,13 @@ jobs:
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -76,7 +88,7 @@ jobs:
- name: Generate tags
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
if: github.ref != 'refs/heads/main'
with:
images: |
@@ -122,7 +134,7 @@ jobs:
- name: Generate tags ultra-lite
id: meta2
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
if: github.ref != 'refs/heads/main'
with:
images: |
@@ -153,7 +165,7 @@ jobs:
- name: Generate tags fat
id: meta3
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
with:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
+8 -8
View File
@@ -23,11 +23,11 @@ jobs:
version: ${{ steps.versionNumber.outputs.versionNumber }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
@@ -35,7 +35,7 @@ jobs:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
with:
gradle-version: 8.14
@@ -83,19 +83,19 @@ jobs:
file_suffix: ""
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Download build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: binaries${{ matrix.file_suffix }}
- name: Display structure of downloaded files
run: ls -R
- name: Install Cosign
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
- name: Generate key pair
run: cosign generate-key-pair
@@ -161,12 +161,12 @@ jobs:
file_suffix: ""
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Download signed artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: signed${{ matrix.file_suffix }}
+3 -3
View File
@@ -34,12 +34,12 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.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@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5
with:
sarif_file: results.sarif
+15 -3
View File
@@ -9,6 +9,18 @@ on:
- main
workflow_dispatch:
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
pull-requests: read
actions: read
@@ -18,16 +30,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
fetch-depth: 0
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
- name: Build and analyze with Gradle
env:
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
+15 -3
View File
@@ -6,6 +6,18 @@ on:
branches:
- master
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
contents: read
@@ -14,11 +26,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
@@ -26,7 +38,7 @@ jobs:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
- name: Generate Swagger documentation
run: ./gradlew :stirling-pdf:generateOpenApiDocs
+14 -2
View File
@@ -12,6 +12,18 @@ on:
- "app/core/src/main/resources/static/3rdPartyLicenses.json"
- "scripts/ignore_translation.toml"
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
contents: read
@@ -20,11 +32,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Setup GitHub App Bot
id: setup-bot
+28 -6
View File
@@ -4,6 +4,18 @@ on:
push:
branches: ["master", "UITest", "testdriver"]
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
contents: read
@@ -12,12 +24,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up JDK
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
@@ -25,6 +37,11 @@ jobs:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
with:
gradle-version: 8.14
- name: Build with Gradle
run: ./gradlew clean build
env:
@@ -40,7 +57,7 @@ jobs:
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
@@ -105,11 +122,16 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
cache: 'npm'
- name: Run TestDriver.ai
uses: testdriverai/action@f0d0f45fdd684db628baa843fe9313f3ca3a8aa8 #1.1.3
@@ -134,7 +156,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
+5 -5
View File
@@ -27,7 +27,7 @@ clientWebUI/
!cucumber/exampleFiles/
!cucumber/exampleFiles/example_html.zip
exampleYmlFiles/stirling/
stirling/
/stirling/
/testing/file_snapshots
SwaggerDoc.json
@@ -125,10 +125,10 @@ SwaggerDoc.json
*.tar.gz
*.rar
*.db
/build
/app/core/build
/app/common/build
/app/proprietary/build
build
app/core/build
app/common/build
app/proprietary/build
common/build
proprietary/build
stirling-pdf/build
+5 -5
View File
@@ -1,15 +1,15 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.0
rev: v0.12.7
hooks:
- id: ruff
args:
- --fix
- --line-length=127
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
files: ^((\.github/scripts|scripts|app/core/src/main/resources/static/python)/.+)?[^/]+\.py$
exclude: (split_photos.py)
- id: ruff-format
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
files: ^((\.github/scripts|scripts|app/core/src/main/resources/static/python)/.+)?[^/]+\.py$
exclude: (split_photos.py)
- repo: https://github.com/codespell-project/codespell
rev: v2.4.1
@@ -22,7 +22,7 @@ repos:
files: \.(html|css|js|py|md)$
exclude: (.vscode|.devcontainer|app/core/src/main/resources|app/proprietary/src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
- repo: https://github.com/gitleaks/gitleaks
rev: v8.27.2
rev: v8.28.0
hooks:
- id: gitleaks
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -43,4 +43,4 @@ repos:
# - stylelint-config-standard@38.0.0
# - "@stylistic/stylelint-plugin@3.1.3"
# files: \.(css)$
# args: [--fix]
# args: [--fix]
+2 -1
View File
@@ -2,6 +2,7 @@
"editor.wordSegmenterLocales": "",
"editor.guides.bracketPairs": "active",
"editor.guides.bracketPairsHorizontal": "active",
"editor.defaultFormatter": "EditorConfig.EditorConfig",
"cSpell.enabled": false,
"[feature]": {
"editor.defaultFormatter": "alexkrechik.cucumberautocomplete"
@@ -40,7 +41,7 @@
"java.configuration.updateBuildConfiguration": "interactive",
"java.format.enabled": true,
"java.format.settings.profile": "GoogleStyle",
"java.format.settings.google.version": "1.27.0",
"java.format.settings.google.version": "1.28.0",
"java.format.settings.google.extra": "--aosp --skip-sorting-imports --skip-javadoc-formatting",
// (DE) Aktiviert Kommentare im Java-Format.
// (EN) Enables comments in Java formatting.
+72 -8
View File
@@ -59,12 +59,73 @@ Frontend designed for **stateful document processing**:
Without cleanup: browser crashes with memory leaks.
#### Tool Development
- **Pattern**: Follow `src/tools/Split.tsx` as reference implementation
- **File Access**: Tools receive `selectedFiles` prop (computed from activeFiles based on user selection)
- **File Selection**: Users select files in FileEditor (tool mode) → stored as IDs → computed to File objects for tools
- **Integration**: All files are part of FileContext ecosystem - automatic memory management and operation tracking
- **Parameters**: Tool parameter handling patterns still being standardized
- **Preview Integration**: Tools can implement preview functionality (see Split tool's thumbnail preview)
**Architecture**: Modular hook-based system with clear separation of concerns:
- **useToolOperation** (`frontend/src/hooks/tools/shared/useToolOperation.ts`): Main orchestrator hook
- Coordinates all tool operations with consistent interface
- Integrates with FileContext for operation tracking
- Handles validation, error handling, and UI state management
- **Supporting Hooks**:
- **useToolState**: UI state management (loading, progress, error, files)
- **useToolApiCalls**: HTTP requests and file processing
- **useToolResources**: Blob URLs, thumbnails, ZIP downloads
- **Utilities**:
- **toolErrorHandler**: Standardized error extraction and i18n support
- **toolResponseProcessor**: API response handling (single/zip/custom)
- **toolOperationTracker**: FileContext integration utilities
**Three Tool Patterns**:
**Pattern 1: Single-File Tools** (Individual processing)
- Backend processes one file per API call
- Set `multiFileEndpoint: false`
- Examples: Compress, Rotate
```typescript
return useToolOperation({
operationType: 'compress',
endpoint: '/api/v1/misc/compress-pdf',
buildFormData: (params, file: File) => { /* single file */ },
multiFileEndpoint: false,
filePrefix: 'compressed_'
});
```
**Pattern 2: Multi-File Tools** (Batch processing)
- Backend accepts `MultipartFile[]` arrays in single API call
- Set `multiFileEndpoint: true`
- Examples: Split, Merge, Overlay
```typescript
return useToolOperation({
operationType: 'split',
endpoint: '/api/v1/general/split-pages',
buildFormData: (params, files: File[]) => { /* all files */ },
multiFileEndpoint: true,
filePrefix: 'split_'
});
```
**Pattern 3: Complex Tools** (Custom processing)
- Tools with complex routing logic or non-standard processing
- Provide `customProcessor` for full control
- Examples: Convert, OCR
```typescript
return useToolOperation({
operationType: 'convert',
customProcessor: async (params, files) => { /* custom logic */ },
filePrefix: 'converted_'
});
```
**Benefits**:
- **No Timeouts**: Operations run until completion (supports 100GB+ files)
- **Consistent**: All tools follow same pattern and interface
- **Maintainable**: Single responsibility hooks, easy to test and modify
- **i18n Ready**: Built-in internationalization support
- **Type Safe**: Full TypeScript support with generic interfaces
- **Memory Safe**: Automatic resource cleanup and blob URL management
## Architecture Overview
@@ -126,7 +187,10 @@ Without cleanup: browser crashes with memory leaks.
- **Core Status**: React SPA architecture complete with multi-tool workflow support
- **State Management**: FileContext handles all file operations and tool navigation
- **File Processing**: Production-ready with memory management for large PDF workflows (up to 100GB+)
- **Tool Integration**: Standardized tool interface - see `src/tools/Split.tsx` as reference
- **Tool Integration**: Modular hook architecture with `useToolOperation` orchestrator
- Individual hooks: `useToolState`, `useToolApiCalls`, `useToolResources`
- Utilities: `toolErrorHandler`, `toolResponseProcessor`, `toolOperationTracker`
- Pattern: Each tool creates focused operation hook, UI consumes state/actions
- **Preview System**: Tool results can be previewed without polluting file context (Split tool example)
- **Performance**: Web Worker thumbnails, IndexedDB persistence, background processing
@@ -141,7 +205,7 @@ Without cleanup: browser crashes with memory leaks.
- **Security**: When `DOCKER_ENABLE_SECURITY=false`, security-related classes are excluded from compilation
- **FileContext**: All file operations MUST go through FileContext - never bypass with direct File handling
- **Memory Management**: Manual cleanup required for PDF.js documents and blob URLs - don't remove cleanup code
- **Tool Development**: New tools should follow Split tool pattern (`src/tools/Split.tsx`)
- **Tool Development**: New tools should follow `useToolOperation` hook pattern (see `useCompressOperation.ts`)
- **Performance Target**: Must handle PDFs up to 100GB+ without browser crashes
- **Preview System**: Tools can preview results without polluting main file context (see Split tool implementation)
+37 -37
View File
@@ -116,47 +116,47 @@ Stirling-PDF currently supports 40 languages!
| Language | Progress |
| -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![63%](https://geps.dev/progress/63) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![63%](https://geps.dev/progress/63) |
| Basque (Euskara) (eu_ES) | ![37%](https://geps.dev/progress/37) |
| Bulgarian (Български) (bg_BG) | ![70%](https://geps.dev/progress/70) |
| Catalan (Català) (ca_CA) | ![69%](https://geps.dev/progress/69) |
| Croatian (Hrvatski) (hr_HR) | ![62%](https://geps.dev/progress/62) |
| Czech (Česky) (cs_CZ) | ![71%](https://geps.dev/progress/71) |
| Danish (Dansk) (da_DK) | ![63%](https://geps.dev/progress/63) |
| Dutch (Nederlands) (nl_NL) | ![61%](https://geps.dev/progress/61) |
| Arabic (العربية) (ar_AR) | ![61%](https://geps.dev/progress/61) |
| 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) |
| 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) | ![91%](https://geps.dev/progress/91) |
| German (Deutsch) (de_DE) | ![100%](https://geps.dev/progress/100) |
| Greek (Ελληνικά) (el_GR) | ![69%](https://geps.dev/progress/69) |
| Hindi (हिंदी) (hi_IN) | ![68%](https://geps.dev/progress/68) |
| French (Français) (fr_FR) | ![89%](https://geps.dev/progress/89) |
| 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) |
| Hungarian (Magyar) (hu_HU) | ![99%](https://geps.dev/progress/99) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![63%](https://geps.dev/progress/63) |
| Irish (Gaeilge) (ga_IE) | ![70%](https://geps.dev/progress/70) |
| 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) | ![95%](https://geps.dev/progress/95) |
| Korean (한국어) (ko_KR) | ![69%](https://geps.dev/progress/69) |
| Norwegian (Norsk) (no_NB) | ![67%](https://geps.dev/progress/67) |
| Persian (فارسی) (fa_IR) | ![66%](https://geps.dev/progress/66) |
| Polish (Polski) (pl_PL) | ![73%](https://geps.dev/progress/73) |
| Portuguese (Português) (pt_PT) | ![70%](https://geps.dev/progress/70) |
| Portuguese Brazilian (Português) (pt_BR) | ![77%](https://geps.dev/progress/77) |
| Romanian (Română) (ro_RO) | ![59%](https://geps.dev/progress/59) |
| Russian (Русский) (ru_RU) | ![70%](https://geps.dev/progress/70) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![97%](https://geps.dev/progress/97) |
| Simplified Chinese (简体中文) (zh_CN) | ![95%](https://geps.dev/progress/95) |
| Slovakian (Slovensky) (sk_SK) | ![53%](https://geps.dev/progress/53) |
| Slovenian (Slovenščina) (sl_SI) | ![73%](https://geps.dev/progress/73) |
| Spanish (Español) (es_ES) | ![75%](https://geps.dev/progress/75) |
| Swedish (Svenska) (sv_SE) | ![67%](https://geps.dev/progress/67) |
| Thai (ไทย) (th_TH) | ![60%](https://geps.dev/progress/60) |
| Tibetan (བོད་ཡིག་) (bo_CN) | ![66%](https://geps.dev/progress/66) |
| Traditional Chinese (繁體中文) (zh_TW) | ![77%](https://geps.dev/progress/77) |
| Turkish (Türkçe) (tr_TR) | ![82%](https://geps.dev/progress/82) |
| Ukrainian (Українська) (uk_UA) | ![72%](https://geps.dev/progress/72) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![58%](https://geps.dev/progress/58) |
| Malayalam (മലയാളം) (ml_IN) | ![75%](https://geps.dev/progress/75) |
| Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) |
| 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 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) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![95%](https://geps.dev/progress/95) |
| Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) |
| Slovakian (Slovensky) (sk_SK) | ![51%](https://geps.dev/progress/51) |
| Slovenian (Slovenščina) (sl_SI) | ![71%](https://geps.dev/progress/71) |
| Spanish (Español) (es_ES) | ![74%](https://geps.dev/progress/74) |
| 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) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![57%](https://geps.dev/progress/57) |
| Malayalam (മലയാളം) (ml_IN) | ![73%](https://geps.dev/progress/73) |
## Stirling PDF Enterprise
+16 -4
View File
@@ -4,7 +4,7 @@ bootRun {
}
spotless {
java {
target sourceSets.main.allJava
target 'src/**/java/**/*.java'
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
@@ -13,11 +13,23 @@ spotless {
leadingTabsToSpaces()
endWithNewline()
}
yaml {
target '**/*.yml', '**/*.yaml'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
format 'gradle', {
target '**/gradle/*.gradle', '**/*.gradle'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
}
dependencies {
api 'org.springframework.boot:spring-boot-starter-web'
api 'org.springframework.boot:spring-boot-starter-aop'
api 'org.springframework.boot:spring-boot-starter-thymeleaf'
// api 'org.springframework.boot:spring-boot-starter-thymeleaf' // Deprecated - UI moved to React frontend
api 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
api 'com.fathzer:javaluator:3.0.6'
api 'com.posthog.java:posthog:1.2.0'
@@ -26,8 +38,8 @@ dependencies {
api 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
api 'org.snakeyaml:snakeyaml-engine:2.9'
api 'org.snakeyaml:snakeyaml-engine:2.10'
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9"
api 'jakarta.mail:jakarta.mail-api:2.1.3'
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.3'
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.4'
}
@@ -8,9 +8,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@@ -19,9 +19,7 @@ import org.springframework.context.annotation.Scope;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;
import org.thymeleaf.spring6.SpringTemplateEngine;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -51,6 +49,15 @@ public class AppConfig {
@Value("${server.port:8080}")
private String serverPort;
@Value("${v2}")
public boolean v2Enabled;
@Bean
public boolean v2Enabled() {
return v2Enabled;
}
/* Commented out Thymeleaf template engine bean - to be removed when frontend migration is complete
@Bean
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
@@ -58,6 +65,7 @@ public class AppConfig {
templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader));
return templateEngine;
}
*/
@Bean(name = "loginEnabled")
public boolean loginEnabled() {
@@ -120,7 +128,7 @@ public class AppConfig {
public boolean rateLimit() {
String rateLimit = System.getProperty("rateLimit");
if (rateLimit == null) rateLimit = System.getenv("rateLimit");
return (rateLimit != null) ? Boolean.valueOf(rateLimit) : false;
return Boolean.parseBoolean(rateLimit);
}
@Bean(name = "RunningInDocker")
@@ -140,8 +148,8 @@ public class AppConfig {
if (!Files.exists(mountInfo)) {
return true;
}
try {
return Files.lines(mountInfo).anyMatch(line -> line.contains(" /configs "));
try (Stream<String> lines = Files.lines(mountInfo)) {
return lines.anyMatch(line -> line.contains(" /configs "));
} catch (IOException e) {
return false;
}
@@ -1,19 +1,8 @@
package stirling.software.common.configuration;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/* Commented out entire FileFallbackTemplateResolver class - Thymeleaf dependency removed
* This class will be removed when frontend migration to React is complete
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver;
import org.thymeleaf.templateresource.FileTemplateResource;
import org.thymeleaf.templateresource.ITemplateResource;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.InputStreamTemplateResource;
@Slf4j
public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver {
@@ -57,3 +46,4 @@ public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateRe
return null;
}
}
*/
@@ -14,6 +14,8 @@ public class InstallationPathConfig {
private static final String CONFIG_PATH;
private static final String CUSTOM_FILES_PATH;
private static final String CLIENT_WEBUI_PATH;
private static final String SCRIPTS_PATH;
private static final String PIPELINE_PATH;
// Config paths
private static final String SETTINGS_PATH;
@@ -23,6 +25,7 @@ public class InstallationPathConfig {
private static final String STATIC_PATH;
private static final String TEMPLATES_PATH;
private static final String SIGNATURES_PATH;
private static final String PRIVATE_KEY_PATH;
static {
BASE_PATH = initializeBasePath();
@@ -32,15 +35,18 @@ public class InstallationPathConfig {
CONFIG_PATH = BASE_PATH + "configs" + File.separator;
CUSTOM_FILES_PATH = BASE_PATH + "customFiles" + File.separator;
CLIENT_WEBUI_PATH = BASE_PATH + "clientWebUI" + File.separator;
PIPELINE_PATH = BASE_PATH + "pipeline" + File.separator;
// Initialize config paths
SETTINGS_PATH = CONFIG_PATH + "settings.yml";
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
SCRIPTS_PATH = CONFIG_PATH + "scripts" + File.separator;
// Initialize custom file paths
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator;
PRIVATE_KEY_PATH = CONFIG_PATH + "db" + File.separator + "keys" + File.separator;
}
private static String initializeBasePath() {
@@ -89,6 +95,14 @@ public class InstallationPathConfig {
return CLIENT_WEBUI_PATH;
}
public static String getScriptsPath() {
return SCRIPTS_PATH;
}
public static String getPipelinePath() {
return PIPELINE_PATH;
}
public static String getSettingsPath() {
return SETTINGS_PATH;
}
@@ -108,4 +122,8 @@ public class InstallationPathConfig {
public static String getSignaturesPath() {
return SIGNATURES_PATH;
}
public static String getPrivateKeyPath() {
return PRIVATE_KEY_PATH;
}
}
@@ -25,6 +25,9 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@@ -58,7 +61,10 @@ public class ApplicationProperties {
private Mail mail = new Mail();
private Premium premium = new Premium();
@JsonIgnore // Deprecated - completely hidden from JSON serialization
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
private AutoPipeline autoPipeline = new AutoPipeline();
private ProcessExecutor processExecutor = new ProcessExecutor();
@@ -113,6 +119,7 @@ public class ApplicationProperties {
private long loginResetTimeMinutes;
private String loginMethod = "all";
private String customGlobalAPIKey;
private Jwt jwt = new Jwt();
public Boolean isAltLogin() {
return saml2.getEnabled() || oauth2.getEnabled();
@@ -168,17 +175,30 @@ public class ApplicationProperties {
private Boolean autoCreateUser = false;
private Boolean blockRegistration = false;
private String registrationId = "stirling";
@ToString.Exclude private String idpMetadataUri;
@ToString.Exclude
@JsonProperty("idpMetadataUri")
private String idpMetadataUri;
private String idpSingleLogoutUrl;
private String idpSingleLoginUrl;
private String idpIssuer;
private String idpCert;
@ToString.Exclude private String privateKey;
@ToString.Exclude private String spCert;
@JsonProperty("idpCert")
private String idpCert;
@ToString.Exclude
@JsonProperty("privateKey")
private String privateKey;
@ToString.Exclude
@JsonProperty("spCert")
private String spCert;
@JsonIgnore
public InputStream getIdpMetadataUri() throws IOException {
if (idpMetadataUri.startsWith("classpath:")) {
return new ClassPathResource(idpMetadataUri.substring("classpath".length()))
return new ClassPathResource(idpMetadataUri.substring("classpath:".length()))
.getInputStream();
}
try {
@@ -192,6 +212,7 @@ public class ApplicationProperties {
}
}
@JsonIgnore
public Resource getSpCert() {
if (spCert == null) return null;
if (spCert.startsWith("classpath:")) {
@@ -201,6 +222,7 @@ public class ApplicationProperties {
}
}
@JsonIgnore
public Resource getIdpCert() {
if (idpCert == null) return null;
if (idpCert.startsWith("classpath:")) {
@@ -210,7 +232,9 @@ public class ApplicationProperties {
}
}
@JsonIgnore
public Resource getPrivateKey() {
if (privateKey == null) return null;
if (privateKey.startsWith("classpath:")) {
return new ClassPathResource(privateKey.substring("classpath:".length()));
} else {
@@ -275,6 +299,15 @@ public class ApplicationProperties {
}
}
}
@Data
public static class Jwt {
private boolean enableKeystore = true;
private boolean enableKeyRotation = false;
private boolean enableKeyCleanup = true;
private int keyRetentionDays = 7;
private boolean secureCookie;
}
}
@Data
@@ -289,7 +322,9 @@ public class ApplicationProperties {
private Boolean enableAnalytics;
private Datasource datasource;
private Boolean disableSanitize;
private int maxDPI;
private Boolean enableUrlToPDF;
private Html html = new Html();
private CustomPaths customPaths = new CustomPaths();
private String fileUploadLimit;
private TempFileManagement tempFileManagement = new TempFileManagement();
@@ -320,8 +355,12 @@ public class ApplicationProperties {
@Data
public static class TempFileManagement {
@JsonProperty("baseTmpDir")
private String baseTmpDir = "";
@JsonProperty("libreofficeDir")
private String libreofficeDir = "";
private String systemTempDir = "";
private String prefix = "stirling-pdf-";
private long maxAgeHours = 24;
@@ -329,12 +368,15 @@ public class ApplicationProperties {
private boolean startupCleanup = true;
private boolean cleanupSystemTemp = false;
@JsonIgnore
public String getBaseTmpDir() {
return baseTmpDir != null && !baseTmpDir.isEmpty()
? baseTmpDir
: java.lang.System.getProperty("java.io.tmpdir") + "/stirling-pdf";
: java.lang.System.getProperty("java.io.tmpdir").replaceAll("/+$", "")
+ "/stirling-pdf";
}
@JsonIgnore
public String getLibreofficeDir() {
return libreofficeDir != null && !libreofficeDir.isEmpty()
? libreofficeDir
@@ -342,6 +384,25 @@ public class ApplicationProperties {
}
}
@Data
public static class Html {
private UrlSecurity urlSecurity = new UrlSecurity();
@Data
public static class UrlSecurity {
private boolean enabled = true;
private String level = "MEDIUM"; // MAX, MEDIUM, OFF
private List<String> allowedDomains = new ArrayList<>();
private List<String> blockedDomains = new ArrayList<>();
private List<String> internalTlds =
Arrays.asList(".local", ".internal", ".corp", ".home");
private boolean blockPrivateNetworks = true;
private boolean blockLocalhost = true;
private boolean blockLinkLocal = true;
private boolean blockCloudMetadata = true;
}
}
@Data
public static class Datasource {
private boolean enableCustomDatabase;
@@ -591,12 +652,24 @@ public class ApplicationProperties {
@Data
public static class TimeoutMinutes {
@JsonProperty("libreOfficetimeoutMinutes")
private long libreOfficeTimeoutMinutes;
@JsonProperty("pdfToHtmltimeoutMinutes")
private long pdfToHtmlTimeoutMinutes;
@JsonProperty("pythonOpenCvtimeoutMinutes")
private long pythonOpenCvTimeoutMinutes;
@JsonProperty("weasyPrinttimeoutMinutes")
private long weasyPrintTimeoutMinutes;
@JsonProperty("installApptimeoutMinutes")
private long installAppTimeoutMinutes;
@JsonProperty("calibretimeoutMinutes")
private long calibreTimeoutMinutes;
private long tesseractTimeoutMinutes;
private long qpdfTimeoutMinutes;
private long ghostscriptTimeoutMinutes;
@@ -1,11 +1,8 @@
package stirling.software.common.model;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
/* Commented out entire InputStreamTemplateResource class - Thymeleaf dependency removed
* This class will be removed when frontend migration to React is complete
import org.thymeleaf.templateresource.ITemplateResource;
public class InputStreamTemplateResource implements ITemplateResource {
private InputStream inputStream;
@@ -42,3 +39,4 @@ public class InputStreamTemplateResource implements ITemplateResource {
return inputStream != null;
}
}
*/
@@ -0,0 +1,208 @@
package stirling.software.common.service;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.regex.Pattern;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
@Service
@RequiredArgsConstructor
@Slf4j
public class SsrfProtectionService {
private final ApplicationProperties applicationProperties;
private static final Pattern DATA_URL_PATTERN =
Pattern.compile("^data:.*", Pattern.CASE_INSENSITIVE);
private static final Pattern FRAGMENT_PATTERN = Pattern.compile("^#.*");
public enum SsrfProtectionLevel {
OFF, // No SSRF protection - allows all URLs
MEDIUM, // Block internal networks but allow external URLs
MAX // Block all external URLs - only data: and fragments
}
public boolean isUrlAllowed(String url) {
ApplicationProperties.Html.UrlSecurity config =
applicationProperties.getSystem().getHtml().getUrlSecurity();
if (!config.isEnabled()) {
return true;
}
if (url == null || url.trim().isEmpty()) {
return false;
}
String trimmedUrl = url.trim();
// Always allow data URLs and fragments
if (DATA_URL_PATTERN.matcher(trimmedUrl).matches()
|| FRAGMENT_PATTERN.matcher(trimmedUrl).matches()) {
return true;
}
SsrfProtectionLevel level = parseProtectionLevel(config.getLevel());
switch (level) {
case OFF:
return true;
case MAX:
return isMaxSecurityAllowed(trimmedUrl, config);
case MEDIUM:
return isMediumSecurityAllowed(trimmedUrl, config);
default:
return false;
}
}
private SsrfProtectionLevel parseProtectionLevel(String level) {
try {
return SsrfProtectionLevel.valueOf(level.toUpperCase());
} catch (IllegalArgumentException e) {
log.warn("Invalid SSRF protection level '{}', defaulting to MEDIUM", level);
return SsrfProtectionLevel.MEDIUM;
}
}
private boolean isMaxSecurityAllowed(
String url, ApplicationProperties.Html.UrlSecurity config) {
// MAX security: only allow explicitly whitelisted domains
try {
URI uri = new URI(url);
String host = uri.getHost();
if (host == null) {
return false;
}
return config.getAllowedDomains().contains(host.toLowerCase());
} catch (Exception e) {
log.debug("Failed to parse URL for MAX security check: {}", url, e);
return false;
}
}
private boolean isMediumSecurityAllowed(
String url, ApplicationProperties.Html.UrlSecurity config) {
try {
URI uri = new URI(url);
String host = uri.getHost();
if (host == null) {
return false;
}
String hostLower = host.toLowerCase();
// Check explicit blocked domains
if (config.getBlockedDomains().contains(hostLower)) {
log.debug("URL blocked by explicit domain blocklist: {}", url);
return false;
}
// Check internal TLD patterns
for (String tld : config.getInternalTlds()) {
if (hostLower.endsWith(tld.toLowerCase())) {
log.debug("URL blocked by internal TLD pattern '{}': {}", tld, url);
return false;
}
}
// If allowedDomains is specified, only allow those
if (!config.getAllowedDomains().isEmpty()) {
boolean isAllowed =
config.getAllowedDomains().stream()
.anyMatch(
domain ->
hostLower.equals(domain.toLowerCase())
|| hostLower.endsWith(
"." + domain.toLowerCase()));
if (!isAllowed) {
log.debug("URL not in allowed domains list: {}", url);
return false;
}
}
// Resolve hostname to IP address for network-based checks
try {
InetAddress address = InetAddress.getByName(host);
if (config.isBlockPrivateNetworks() && isPrivateAddress(address)) {
log.debug("URL blocked - private network address: {}", url);
return false;
}
if (config.isBlockLocalhost() && address.isLoopbackAddress()) {
log.debug("URL blocked - localhost address: {}", url);
return false;
}
if (config.isBlockLinkLocal() && address.isLinkLocalAddress()) {
log.debug("URL blocked - link-local address: {}", url);
return false;
}
if (config.isBlockCloudMetadata()
&& isCloudMetadataAddress(address.getHostAddress())) {
log.debug("URL blocked - cloud metadata endpoint: {}", url);
return false;
}
} catch (UnknownHostException e) {
log.debug("Failed to resolve hostname for SSRF check: {}", host, e);
return false;
}
return true;
} catch (Exception e) {
log.debug("Failed to parse URL for MEDIUM security check: {}", url, e);
return false;
}
}
private boolean isPrivateAddress(InetAddress address) {
return address.isSiteLocalAddress()
|| address.isAnyLocalAddress()
|| isPrivateIPv4Range(address.getHostAddress());
}
private boolean isPrivateIPv4Range(String ip) {
return ip.startsWith("10.")
|| ip.startsWith("192.168.")
|| (ip.startsWith("172.") && isInRange172(ip))
|| ip.startsWith("127.")
|| "0.0.0.0".equals(ip);
}
private boolean isInRange172(String ip) {
String[] parts = ip.split("\\.");
if (parts.length >= 2) {
try {
int secondOctet = Integer.parseInt(parts[1]);
return secondOctet >= 16 && secondOctet <= 31;
} catch (NumberFormatException e) {
return false;
}
}
return false;
}
private boolean isCloudMetadataAddress(String ip) {
// Cloud metadata endpoints for AWS, GCP, Azure, Oracle Cloud, and IBM Cloud
return ip.startsWith("169.254.169.254") // AWS/GCP/Azure
|| ip.startsWith("fd00:ec2::254") // AWS IPv6
|| ip.startsWith("169.254.169.253") // Oracle Cloud
|| ip.startsWith("169.254.169.250"); // IBM Cloud
}
}
@@ -1,21 +1,71 @@
package stirling.software.common.util;
import org.owasp.html.AttributePolicy;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.SsrfProtectionService;
@Component
public class CustomHtmlSanitizer {
private static final PolicyFactory POLICY =
private final SsrfProtectionService ssrfProtectionService;
private final ApplicationProperties applicationProperties;
@Autowired
public CustomHtmlSanitizer(
SsrfProtectionService ssrfProtectionService,
ApplicationProperties applicationProperties) {
this.ssrfProtectionService = ssrfProtectionService;
this.applicationProperties = applicationProperties;
}
private final AttributePolicy SSRF_SAFE_URL_POLICY =
new AttributePolicy() {
@Override
public String apply(String elementName, String attributeName, String value) {
if (value == null || value.trim().isEmpty()) {
return null;
}
String trimmedValue = value.trim();
// Use the SSRF protection service to validate the URL
if (ssrfProtectionService != null
&& !ssrfProtectionService.isUrlAllowed(trimmedValue)) {
return null;
}
return trimmedValue;
}
};
private final PolicyFactory SSRF_SAFE_IMAGES_POLICY =
new HtmlPolicyBuilder()
.allowElements("img")
.allowAttributes("alt", "width", "height", "title")
.onElements("img")
.allowAttributes("src")
.matching(SSRF_SAFE_URL_POLICY)
.onElements("img")
.toFactory();
private final PolicyFactory POLICY =
Sanitizers.FORMATTING
.and(Sanitizers.BLOCKS)
.and(Sanitizers.STYLES)
.and(Sanitizers.LINKS)
.and(Sanitizers.TABLES)
.and(Sanitizers.IMAGES)
.and(SSRF_SAFE_IMAGES_POLICY)
.and(new HtmlPolicyBuilder().disallowElements("noscript").toFactory());
public static String sanitize(String html) {
String htmlAfter = POLICY.sanitize(html);
return htmlAfter;
public String sanitize(String html) {
boolean disableSanitize =
Boolean.TRUE.equals(applicationProperties.getSystem().getDisableSanitize());
return disableSanitize ? html : POLICY.sanitize(html);
}
}
@@ -0,0 +1,652 @@
package stirling.software.common.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;
import lombok.Data;
import lombok.experimental.UtilityClass;
import stirling.software.common.model.api.converters.EmlToPdfRequest;
@UtilityClass
public class EmlParser {
private static volatile Boolean jakartaMailAvailable = null;
private static volatile Method mimeUtilityDecodeTextMethod = null;
private static volatile boolean mimeUtilityChecked = false;
private static final Pattern MIME_ENCODED_PATTERN =
Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
private static final String DISPOSITION_ATTACHMENT = "attachment";
private static final String TEXT_PLAIN = "text/plain";
private static final String TEXT_HTML = "text/html";
private static final String MULTIPART_PREFIX = "multipart/";
private static final String HEADER_CONTENT_TYPE = "content-type:";
private static final String HEADER_CONTENT_DISPOSITION = "content-disposition:";
private static final String HEADER_CONTENT_TRANSFER_ENCODING = "content-transfer-encoding:";
private static final String HEADER_CONTENT_ID = "Content-ID";
private static final String HEADER_SUBJECT = "Subject:";
private static final String HEADER_FROM = "From:";
private static final String HEADER_TO = "To:";
private static final String HEADER_CC = "Cc:";
private static final String HEADER_BCC = "Bcc:";
private static final String HEADER_DATE = "Date:";
private static synchronized boolean isJakartaMailAvailable() {
if (jakartaMailAvailable == null) {
try {
Class.forName("jakarta.mail.internet.MimeMessage");
Class.forName("jakarta.mail.Session");
Class.forName("jakarta.mail.internet.MimeUtility");
Class.forName("jakarta.mail.internet.MimePart");
Class.forName("jakarta.mail.internet.MimeMultipart");
Class.forName("jakarta.mail.Multipart");
Class.forName("jakarta.mail.Part");
jakartaMailAvailable = true;
} catch (ClassNotFoundException e) {
jakartaMailAvailable = false;
}
}
return jakartaMailAvailable;
}
public static EmailContent extractEmailContent(
byte[] emlBytes, EmlToPdfRequest request, CustomHtmlSanitizer customHtmlSanitizer)
throws IOException {
EmlProcessingUtils.validateEmlInput(emlBytes);
if (isJakartaMailAvailable()) {
return extractEmailContentAdvanced(emlBytes, request, customHtmlSanitizer);
} else {
return extractEmailContentBasic(emlBytes, request, customHtmlSanitizer);
}
}
private static EmailContent extractEmailContentBasic(
byte[] emlBytes, EmlToPdfRequest request, CustomHtmlSanitizer customHtmlSanitizer) {
String emlContent = new String(emlBytes, StandardCharsets.UTF_8);
EmailContent content = new EmailContent();
content.setSubject(extractBasicHeader(emlContent, HEADER_SUBJECT));
content.setFrom(extractBasicHeader(emlContent, HEADER_FROM));
content.setTo(extractBasicHeader(emlContent, HEADER_TO));
content.setCc(extractBasicHeader(emlContent, HEADER_CC));
content.setBcc(extractBasicHeader(emlContent, HEADER_BCC));
String dateStr = extractBasicHeader(emlContent, HEADER_DATE);
if (!dateStr.isEmpty()) {
content.setDateString(dateStr);
}
String htmlBody = extractHtmlBody(emlContent);
if (htmlBody != null) {
content.setHtmlBody(htmlBody);
} else {
String textBody = extractTextBody(emlContent);
content.setTextBody(textBody != null ? textBody : "Email content could not be parsed");
}
content.getAttachments().addAll(extractAttachmentsBasic(emlContent));
return content;
}
private static EmailContent extractEmailContentAdvanced(
byte[] emlBytes, EmlToPdfRequest request, CustomHtmlSanitizer customHtmlSanitizer) {
try {
Class<?> sessionClass = Class.forName("jakarta.mail.Session");
Class<?> mimeMessageClass = Class.forName("jakarta.mail.internet.MimeMessage");
Method getDefaultInstance =
sessionClass.getMethod("getDefaultInstance", Properties.class);
Object session = getDefaultInstance.invoke(null, new Properties());
Class<?>[] constructorArgs = new Class<?>[] {sessionClass, InputStream.class};
Constructor<?> mimeMessageConstructor =
mimeMessageClass.getConstructor(constructorArgs);
Object message =
mimeMessageConstructor.newInstance(session, new ByteArrayInputStream(emlBytes));
return extractFromMimeMessage(message, request, customHtmlSanitizer);
} catch (ReflectiveOperationException e) {
return extractEmailContentBasic(emlBytes, request, customHtmlSanitizer);
}
}
private static EmailContent extractFromMimeMessage(
Object message, EmlToPdfRequest request, CustomHtmlSanitizer customHtmlSanitizer) {
EmailContent content = new EmailContent();
try {
Class<?> messageClass = message.getClass();
Method getSubject = messageClass.getMethod("getSubject");
String subject = (String) getSubject.invoke(message);
content.setSubject(subject != null ? safeMimeDecode(subject) : "No Subject");
Method getFrom = messageClass.getMethod("getFrom");
Object[] fromAddresses = (Object[]) getFrom.invoke(message);
content.setFrom(buildAddressString(fromAddresses));
extractRecipients(message, messageClass, content);
Method getSentDate = messageClass.getMethod("getSentDate");
content.setDate((Date) getSentDate.invoke(message));
Method getContent = messageClass.getMethod("getContent");
Object messageContent = getContent.invoke(message);
processMessageContent(message, messageContent, content, request, customHtmlSanitizer);
} catch (ReflectiveOperationException | RuntimeException e) {
content.setSubject("Email Conversion");
content.setFrom("Unknown");
content.setTo("Unknown");
content.setCc("");
content.setBcc("");
content.setTextBody("Email content could not be parsed with advanced processing");
}
return content;
}
private static void extractRecipients(
Object message, Class<?> messageClass, EmailContent content) {
try {
Method getRecipients =
messageClass.getMethod(
"getRecipients", Class.forName("jakarta.mail.Message$RecipientType"));
Class<?> recipientTypeClass = Class.forName("jakarta.mail.Message$RecipientType");
Object toType = recipientTypeClass.getField("TO").get(null);
Object[] toRecipients = (Object[]) getRecipients.invoke(message, toType);
content.setTo(buildAddressString(toRecipients));
Object ccType = recipientTypeClass.getField("CC").get(null);
Object[] ccRecipients = (Object[]) getRecipients.invoke(message, ccType);
content.setCc(buildAddressString(ccRecipients));
Object bccType = recipientTypeClass.getField("BCC").get(null);
Object[] bccRecipients = (Object[]) getRecipients.invoke(message, bccType);
content.setBcc(buildAddressString(bccRecipients));
} catch (ReflectiveOperationException e) {
try {
Method getAllRecipients = messageClass.getMethod("getAllRecipients");
Object[] recipients = (Object[]) getAllRecipients.invoke(message);
content.setTo(buildAddressString(recipients));
content.setCc("");
content.setBcc("");
} catch (ReflectiveOperationException ex) {
content.setTo("");
content.setCc("");
content.setBcc("");
}
}
}
private static String buildAddressString(Object[] addresses) {
if (addresses == null || addresses.length == 0) {
return "";
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < addresses.length; i++) {
if (i > 0) builder.append(", ");
builder.append(safeMimeDecode(addresses[i].toString()));
}
return builder.toString();
}
private static void processMessageContent(
Object message,
Object messageContent,
EmailContent content,
EmlToPdfRequest request,
CustomHtmlSanitizer customHtmlSanitizer) {
try {
if (messageContent instanceof String stringContent) {
Method getContentType = message.getClass().getMethod("getContentType");
String contentType = (String) getContentType.invoke(message);
if (contentType != null && contentType.toLowerCase().contains(TEXT_HTML)) {
content.setHtmlBody(stringContent);
} else {
content.setTextBody(stringContent);
}
} else {
Class<?> multipartClass = Class.forName("jakarta.mail.Multipart");
if (multipartClass.isInstance(messageContent)) {
processMultipart(messageContent, content, request, customHtmlSanitizer, 0);
}
}
} catch (ReflectiveOperationException | ClassCastException e) {
content.setTextBody("Email content could not be parsed with advanced processing");
}
}
private static void processMultipart(
Object multipart,
EmailContent content,
EmlToPdfRequest request,
CustomHtmlSanitizer customHtmlSanitizer,
int depth) {
final int MAX_MULTIPART_DEPTH = 10;
if (depth > MAX_MULTIPART_DEPTH) {
content.setHtmlBody("<div class=\"error\">Maximum multipart depth exceeded</div>");
return;
}
try {
Class<?> multipartClass = multipart.getClass();
Method getCount = multipartClass.getMethod("getCount");
int count = (Integer) getCount.invoke(multipart);
Method getBodyPart = multipartClass.getMethod("getBodyPart", int.class);
for (int i = 0; i < count; i++) {
Object part = getBodyPart.invoke(multipart, i);
processPart(part, content, request, customHtmlSanitizer, depth + 1);
}
} catch (ReflectiveOperationException | ClassCastException e) {
content.setHtmlBody("<div class=\"error\">Error processing multipart content</div>");
}
}
private static void processPart(
Object part,
EmailContent content,
EmlToPdfRequest request,
CustomHtmlSanitizer customHtmlSanitizer,
int depth) {
try {
Class<?> partClass = part.getClass();
Method isMimeType = partClass.getMethod("isMimeType", String.class);
Method getContent = partClass.getMethod("getContent");
Method getDisposition = partClass.getMethod("getDisposition");
Method getFileName = partClass.getMethod("getFileName");
Method getContentType = partClass.getMethod("getContentType");
Method getHeader = partClass.getMethod("getHeader", String.class);
Object disposition = getDisposition.invoke(part);
String filename = (String) getFileName.invoke(part);
String contentType = (String) getContentType.invoke(part);
String normalizedDisposition =
disposition != null ? ((String) disposition).toLowerCase() : null;
if ((Boolean) isMimeType.invoke(part, TEXT_PLAIN) && normalizedDisposition == null) {
Object partContent = getContent.invoke(part);
if (partContent instanceof String stringContent) {
content.setTextBody(stringContent);
}
} else if ((Boolean) isMimeType.invoke(part, TEXT_HTML)
&& normalizedDisposition == null) {
Object partContent = getContent.invoke(part);
if (partContent instanceof String stringContent) {
String htmlBody =
customHtmlSanitizer != null
? customHtmlSanitizer.sanitize(stringContent)
: stringContent;
content.setHtmlBody(htmlBody);
}
} else if ((normalizedDisposition != null
&& normalizedDisposition.contains(DISPOSITION_ATTACHMENT))
|| (filename != null && !filename.trim().isEmpty())) {
processAttachment(
part, content, request, getHeader, getContent, filename, contentType);
} else if ((Boolean) isMimeType.invoke(part, "multipart/*")) {
Object multipartContent = getContent.invoke(part);
if (multipartContent != null) {
Class<?> multipartClass = Class.forName("jakarta.mail.Multipart");
if (multipartClass.isInstance(multipartContent)) {
processMultipart(
multipartContent, content, request, customHtmlSanitizer, depth + 1);
}
}
}
} catch (ReflectiveOperationException | RuntimeException e) {
// Continue processing other parts if one fails
}
}
private static void processAttachment(
Object part,
EmailContent content,
EmlToPdfRequest request,
Method getHeader,
Method getContent,
String filename,
String contentType) {
content.setAttachmentCount(content.getAttachmentCount() + 1);
if (filename != null && !filename.trim().isEmpty()) {
EmailAttachment attachment = new EmailAttachment();
attachment.setFilename(safeMimeDecode(filename));
attachment.setContentType(contentType);
try {
String[] contentIdHeaders = (String[]) getHeader.invoke(part, HEADER_CONTENT_ID);
if (contentIdHeaders != null) {
for (String contentIdHeader : contentIdHeaders) {
if (contentIdHeader != null && !contentIdHeader.trim().isEmpty()) {
attachment.setEmbedded(true);
String contentId = contentIdHeader.trim().replaceAll("[<>]", "");
attachment.setContentId(contentId);
break;
}
}
}
} catch (ReflectiveOperationException e) {
}
if ((request != null && request.isIncludeAttachments()) || attachment.isEmbedded()) {
extractAttachmentData(part, attachment, getContent, request);
}
content.getAttachments().add(attachment);
}
}
private static void extractAttachmentData(
Object part, EmailAttachment attachment, Method getContent, EmlToPdfRequest request) {
try {
Object attachmentContent = getContent.invoke(part);
byte[] attachmentData = null;
if (attachmentContent instanceof InputStream inputStream) {
try (InputStream stream = inputStream) {
attachmentData = stream.readAllBytes();
} catch (IOException e) {
if (attachment.isEmbedded()) {
attachmentData = new byte[0];
} else {
throw new RuntimeException(e);
}
}
} else if (attachmentContent instanceof byte[] byteArray) {
attachmentData = byteArray;
} else if (attachmentContent instanceof String stringContent) {
attachmentData = stringContent.getBytes(StandardCharsets.UTF_8);
}
if (attachmentData != null) {
long maxSizeMB = request != null ? request.getMaxAttachmentSizeMB() : 10L;
long maxSizeBytes = maxSizeMB * 1024 * 1024;
if (attachmentData.length <= maxSizeBytes || attachment.isEmbedded()) {
attachment.setData(attachmentData);
attachment.setSizeBytes(attachmentData.length);
} else {
attachment.setSizeBytes(attachmentData.length);
}
}
} catch (ReflectiveOperationException | RuntimeException e) {
// Continue without attachment data
}
}
private static String extractBasicHeader(String emlContent, String headerName) {
try {
String[] lines = emlContent.split("\r?\n");
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
if (line.toLowerCase().startsWith(headerName.toLowerCase())) {
StringBuilder value =
new StringBuilder(line.substring(headerName.length()).trim());
for (int j = i + 1; j < lines.length; j++) {
if (lines[j].startsWith(" ") || lines[j].startsWith("\t")) {
value.append(" ").append(lines[j].trim());
} else {
break;
}
}
return safeMimeDecode(value.toString());
}
if (line.trim().isEmpty()) break;
}
} catch (RuntimeException e) {
// Ignore errors in header extraction
}
return "";
}
private static String extractHtmlBody(String emlContent) {
try {
String lowerContent = emlContent.toLowerCase();
int htmlStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_HTML);
if (htmlStart == -1) return null;
int bodyStart = emlContent.indexOf("\r\n\r\n", htmlStart);
if (bodyStart == -1) bodyStart = emlContent.indexOf("\n\n", htmlStart);
if (bodyStart == -1) return null;
bodyStart += (emlContent.charAt(bodyStart + 1) == '\r') ? 4 : 2;
int bodyEnd = findPartEnd(emlContent, bodyStart);
return emlContent.substring(bodyStart, bodyEnd).trim();
} catch (Exception e) {
return null;
}
}
private static String extractTextBody(String emlContent) {
try {
String lowerContent = emlContent.toLowerCase();
int textStart = lowerContent.indexOf(HEADER_CONTENT_TYPE + " " + TEXT_PLAIN);
if (textStart == -1) {
int bodyStart = emlContent.indexOf("\r\n\r\n");
if (bodyStart == -1) bodyStart = emlContent.indexOf("\n\n");
if (bodyStart != -1) {
bodyStart += (emlContent.charAt(bodyStart + 1) == '\r') ? 4 : 2;
int bodyEnd = findPartEnd(emlContent, bodyStart);
return emlContent.substring(bodyStart, bodyEnd).trim();
}
return null;
}
int bodyStart = emlContent.indexOf("\r\n\r\n", textStart);
if (bodyStart == -1) bodyStart = emlContent.indexOf("\n\n", textStart);
if (bodyStart == -1) return null;
bodyStart += (emlContent.charAt(bodyStart + 1) == '\r') ? 4 : 2;
int bodyEnd = findPartEnd(emlContent, bodyStart);
return emlContent.substring(bodyStart, bodyEnd).trim();
} catch (RuntimeException e) {
return null;
}
}
private static int findPartEnd(String content, int start) {
String[] lines = content.substring(start).split("\r?\n");
StringBuilder result = new StringBuilder();
for (String line : lines) {
if (line.startsWith("--") && line.length() > 10) break;
result.append(line).append("\n");
}
return start + result.length();
}
private static List<EmailAttachment> extractAttachmentsBasic(String emlContent) {
List<EmailAttachment> attachments = new ArrayList<>();
try {
String[] lines = emlContent.split("\r?\n");
boolean inHeaders = true;
String currentContentType = "";
String currentDisposition = "";
String currentFilename = "";
String currentEncoding = "";
for (String line : lines) {
String lowerLine = line.toLowerCase().trim();
if (line.trim().isEmpty()) {
inHeaders = false;
if (isAttachment(currentDisposition, currentFilename, currentContentType)) {
EmailAttachment attachment = new EmailAttachment();
attachment.setFilename(currentFilename);
attachment.setContentType(currentContentType);
attachment.setTransferEncoding(currentEncoding);
attachments.add(attachment);
}
currentContentType = "";
currentDisposition = "";
currentFilename = "";
currentEncoding = "";
inHeaders = true;
continue;
}
if (!inHeaders) continue;
if (lowerLine.startsWith(HEADER_CONTENT_TYPE)) {
currentContentType = line.substring(HEADER_CONTENT_TYPE.length()).trim();
} else if (lowerLine.startsWith(HEADER_CONTENT_DISPOSITION)) {
currentDisposition = line.substring(HEADER_CONTENT_DISPOSITION.length()).trim();
currentFilename = extractFilenameFromDisposition(currentDisposition);
} else if (lowerLine.startsWith(HEADER_CONTENT_TRANSFER_ENCODING)) {
currentEncoding =
line.substring(HEADER_CONTENT_TRANSFER_ENCODING.length()).trim();
}
}
} catch (RuntimeException e) {
// Continue with empty list
}
return attachments;
}
private static boolean isAttachment(String disposition, String filename, String contentType) {
return (disposition.toLowerCase().contains(DISPOSITION_ATTACHMENT) && !filename.isEmpty())
|| (!filename.isEmpty() && !contentType.toLowerCase().startsWith("text/"))
|| (contentType.toLowerCase().contains("application/") && !filename.isEmpty());
}
private static String extractFilenameFromDisposition(String disposition) {
if (disposition == null || !disposition.contains("filename=")) {
return "";
}
// Handle filename*= (RFC 2231 encoded filename)
if (disposition.toLowerCase().contains("filename*=")) {
int filenameStarStart = disposition.toLowerCase().indexOf("filename*=") + 10;
int filenameStarEnd = disposition.indexOf(";", filenameStarStart);
if (filenameStarEnd == -1) filenameStarEnd = disposition.length();
String extendedFilename =
disposition.substring(filenameStarStart, filenameStarEnd).trim();
extendedFilename = extendedFilename.replaceAll("^\"|\"$", "");
if (extendedFilename.contains("'")) {
String[] parts = extendedFilename.split("'", 3);
if (parts.length == 3) {
return EmlProcessingUtils.decodeUrlEncoded(parts[2]);
}
}
}
// Handle regular filename=
int filenameStart = disposition.toLowerCase().indexOf("filename=") + 9;
int filenameEnd = disposition.indexOf(";", filenameStart);
if (filenameEnd == -1) filenameEnd = disposition.length();
String filename = disposition.substring(filenameStart, filenameEnd).trim();
filename = filename.replaceAll("^\"|\"$", "");
return safeMimeDecode(filename);
}
public static String safeMimeDecode(String headerValue) {
if (headerValue == null || headerValue.trim().isEmpty()) {
return "";
}
if (!mimeUtilityChecked) {
synchronized (EmlParser.class) {
if (!mimeUtilityChecked) {
initializeMimeUtilityDecoding();
}
}
}
if (mimeUtilityDecodeTextMethod != null) {
try {
return (String) mimeUtilityDecodeTextMethod.invoke(null, headerValue.trim());
} catch (ReflectiveOperationException | RuntimeException e) {
// Fall through to custom implementation
}
}
return EmlProcessingUtils.decodeMimeHeader(headerValue.trim());
}
private static void initializeMimeUtilityDecoding() {
try {
Class<?> mimeUtilityClass = Class.forName("jakarta.mail.internet.MimeUtility");
mimeUtilityDecodeTextMethod = mimeUtilityClass.getMethod("decodeText", String.class);
} catch (ClassNotFoundException | NoSuchMethodException e) {
mimeUtilityDecodeTextMethod = null;
}
mimeUtilityChecked = true;
}
@Data
public static class EmailContent {
private String subject;
private String from;
private String to;
private String cc;
private String bcc;
private Date date;
private String dateString; // For basic parsing fallback
private String htmlBody;
private String textBody;
private int attachmentCount;
private List<EmailAttachment> attachments = new ArrayList<>();
public void setHtmlBody(String htmlBody) {
this.htmlBody = htmlBody != null ? htmlBody.replaceAll("\r", "") : null;
}
public void setTextBody(String textBody) {
this.textBody = textBody != null ? textBody.replaceAll("\r", "") : null;
}
}
@Data
public static class EmailAttachment {
private String filename;
private String contentType;
private byte[] data;
private boolean embedded;
private String embeddedFilename;
private long sizeBytes;
private String contentId;
private String disposition;
private String transferEncoding;
public void setData(byte[] data) {
this.data = data;
if (data != null) {
this.sizeBytes = data.length;
}
}
}
}
@@ -0,0 +1,601 @@
package stirling.software.common.util;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.experimental.UtilityClass;
import stirling.software.common.model.api.converters.EmlToPdfRequest;
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
@UtilityClass
public class EmlProcessingUtils {
// Style constants
private static final int DEFAULT_FONT_SIZE = 12;
private static final String DEFAULT_FONT_FAMILY = "Helvetica, sans-serif";
private static final float DEFAULT_LINE_HEIGHT = 1.4f;
private static final String DEFAULT_ZOOM = "1.0";
private static final String DEFAULT_TEXT_COLOR = "#202124";
private static final String DEFAULT_BACKGROUND_COLOR = "#ffffff";
private static final String DEFAULT_BORDER_COLOR = "#e8eaed";
private static final String ATTACHMENT_BACKGROUND_COLOR = "#f9f9f9";
private static final String ATTACHMENT_BORDER_COLOR = "#eeeeee";
private static final int EML_CHECK_LENGTH = 8192;
private static final int MIN_HEADER_COUNT_FOR_VALID_EML = 2;
// MIME type detection
private static final Map<String, String> EXTENSION_TO_MIME_TYPE =
Map.of(
".png", "image/png",
".jpg", "image/jpeg",
".jpeg", "image/jpeg",
".gif", "image/gif",
".bmp", "image/bmp",
".webp", "image/webp",
".svg", "image/svg+xml",
".ico", "image/x-icon",
".tiff", "image/tiff",
".tif", "image/tiff");
public static void validateEmlInput(byte[] emlBytes) {
if (emlBytes == null || emlBytes.length == 0) {
throw new IllegalArgumentException("EML file is empty or null");
}
if (isInvalidEmlFormat(emlBytes)) {
throw new IllegalArgumentException("Invalid EML file format");
}
}
private static boolean isInvalidEmlFormat(byte[] emlBytes) {
try {
int checkLength = Math.min(emlBytes.length, EML_CHECK_LENGTH);
String content;
try {
content = new String(emlBytes, 0, checkLength, StandardCharsets.UTF_8);
if (content.contains("\uFFFD")) {
content = new String(emlBytes, 0, checkLength, StandardCharsets.ISO_8859_1);
}
} catch (Exception e) {
content = new String(emlBytes, 0, checkLength, StandardCharsets.ISO_8859_1);
}
String lowerContent = content.toLowerCase(Locale.ROOT);
boolean hasFrom =
lowerContent.contains("from:") || lowerContent.contains("return-path:");
boolean hasSubject = lowerContent.contains("subject:");
boolean hasMessageId = lowerContent.contains("message-id:");
boolean hasDate = lowerContent.contains("date:");
boolean hasTo =
lowerContent.contains("to:")
|| lowerContent.contains("cc:")
|| lowerContent.contains("bcc:");
boolean hasMimeStructure =
lowerContent.contains("multipart/")
|| lowerContent.contains("text/plain")
|| lowerContent.contains("text/html")
|| lowerContent.contains("boundary=");
int headerCount = 0;
if (hasFrom) headerCount++;
if (hasSubject) headerCount++;
if (hasMessageId) headerCount++;
if (hasDate) headerCount++;
if (hasTo) headerCount++;
return headerCount < MIN_HEADER_COUNT_FOR_VALID_EML && !hasMimeStructure;
} catch (RuntimeException e) {
return false;
}
}
public static String generateEnhancedEmailHtml(
EmlParser.EmailContent content,
EmlToPdfRequest request,
CustomHtmlSanitizer customHtmlSanitizer) {
StringBuilder html = new StringBuilder();
html.append(
String.format(
"""
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8">
<title>%s</title>
<style>
""",
sanitizeText(content.getSubject(), customHtmlSanitizer)));
appendEnhancedStyles(html);
html.append(
"""
</style>
</head><body>
""");
html.append(
String.format(
"""
<div class="email-container">
<div class="email-header">
<h1>%s</h1>
<div class="email-meta">
<div><strong>From:</strong> %s</div>
<div><strong>To:</strong> %s</div>
""",
sanitizeText(content.getSubject(), customHtmlSanitizer),
sanitizeText(content.getFrom(), customHtmlSanitizer),
sanitizeText(content.getTo(), customHtmlSanitizer)));
if (content.getCc() != null && !content.getCc().trim().isEmpty()) {
html.append(
String.format(
"<div><strong>CC:</strong> %s</div>\n",
sanitizeText(content.getCc(), customHtmlSanitizer)));
}
if (content.getBcc() != null && !content.getBcc().trim().isEmpty()) {
html.append(
String.format(
"<div><strong>BCC:</strong> %s</div>\n",
sanitizeText(content.getBcc(), customHtmlSanitizer)));
}
if (content.getDate() != null) {
html.append(
String.format(
"<div><strong>Date:</strong> %s</div>\n",
PdfAttachmentHandler.formatEmailDate(content.getDate())));
} else if (content.getDateString() != null && !content.getDateString().trim().isEmpty()) {
html.append(
String.format(
"<div><strong>Date:</strong> %s</div>\n",
sanitizeText(content.getDateString(), customHtmlSanitizer)));
}
html.append("</div></div>\n");
html.append("<div class=\"email-body\">\n");
if (content.getHtmlBody() != null && !content.getHtmlBody().trim().isEmpty()) {
String processedHtml =
processEmailHtmlBody(content.getHtmlBody(), content, customHtmlSanitizer);
html.append(processedHtml);
} else if (content.getTextBody() != null && !content.getTextBody().trim().isEmpty()) {
html.append(
String.format(
"<div class=\"text-body\">%s</div>",
convertTextToHtml(content.getTextBody(), customHtmlSanitizer)));
} else {
html.append("<div class=\"no-content\"><p><em>No content available</em></p></div>");
}
html.append("</div>\n");
if (content.getAttachmentCount() > 0 || !content.getAttachments().isEmpty()) {
appendAttachmentsSection(html, content, request, customHtmlSanitizer);
}
html.append("</div>\n</body></html>");
return html.toString();
}
public static String processEmailHtmlBody(
String htmlBody,
EmlParser.EmailContent emailContent,
CustomHtmlSanitizer customHtmlSanitizer) {
if (htmlBody == null) return "";
String processed =
customHtmlSanitizer != null ? customHtmlSanitizer.sanitize(htmlBody) : htmlBody;
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*fixed[^;]*;?", "");
processed = processed.replaceAll("(?i)\\s*position\\s*:\\s*absolute[^;]*;?", "");
if (emailContent != null && !emailContent.getAttachments().isEmpty()) {
processed = PdfAttachmentHandler.processInlineImages(processed, emailContent);
}
return processed;
}
public static String convertTextToHtml(
String textBody, CustomHtmlSanitizer customHtmlSanitizer) {
if (textBody == null) return "";
String html =
customHtmlSanitizer != null
? customHtmlSanitizer.sanitize(textBody)
: escapeHtml(textBody);
html = html.replace("\r\n", "\n").replace("\r", "\n");
html = html.replace("\n", "<br>\n");
html =
html.replaceAll(
"(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)",
"<a href=\"$1\" style=\"color: #1a73e8; text-decoration: underline;\">$1</a>");
html =
html.replaceAll(
"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,63})",
"<a href=\"mailto:$1\" style=\"color: #1a73e8; text-decoration: underline;\">$1</a>");
return html;
}
private static void appendEnhancedStyles(StringBuilder html) {
String css =
String.format(
"""
body {
font-family: %s;
font-size: %dpx;
line-height: %s;
color: %s;
margin: 0;
padding: 16px;
background-color: %s;
}
.email-container {
width: 100%%;
max-width: 100%%;
margin: 0 auto;
}
.email-header {
padding-bottom: 10px;
border-bottom: 1px solid %s;
margin-bottom: 10px;
}
.email-header h1 {
margin: 0 0 10px 0;
font-size: %dpx;
font-weight: bold;
}
.email-meta div {
margin-bottom: 2px;
font-size: %dpx;
}
.email-body {
word-wrap: break-word;
}
.attachment-section {
margin-top: 15px;
padding: 10px;
background-color: %s;
border: 1px solid %s;
border-radius: 3px;
}
.attachment-section h3 {
margin: 0 0 8px 0;
font-size: %dpx;
}
.attachment-item {
padding: 5px 0;
}
.attachment-icon {
margin-right: 5px;
}
.attachment-details, .attachment-type {
font-size: %dpx;
color: #555555;
}
.attachment-inclusion-note, .attachment-info-note {
margin-top: 8px;
padding: 6px;
font-size: %dpx;
border-radius: 3px;
}
.attachment-inclusion-note {
background-color: #e6ffed;
border: 1px solid #d4f7dc;
color: #006420;
}
.attachment-info-note {
background-color: #fff9e6;
border: 1px solid #fff0c2;
color: #664d00;
}
.attachment-link-container {
display: flex;
align-items: center;
padding: 8px;
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
margin: 4px 0;
}
.attachment-link-container:hover {
background-color: #e9ecef;
}
.attachment-note {
font-size: %dpx;
color: #6c757d;
font-style: italic;
margin-left: 8px;
}
.no-content {
padding: 20px;
text-align: center;
color: #666;
font-style: italic;
}
.text-body {
white-space: pre-wrap;
}
img {
max-width: 100%%;
height: auto;
display: block;
}
""",
DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE,
DEFAULT_LINE_HEIGHT,
DEFAULT_TEXT_COLOR,
DEFAULT_BACKGROUND_COLOR,
DEFAULT_BORDER_COLOR,
DEFAULT_FONT_SIZE + 4,
DEFAULT_FONT_SIZE - 1,
ATTACHMENT_BACKGROUND_COLOR,
ATTACHMENT_BORDER_COLOR,
DEFAULT_FONT_SIZE + 1,
DEFAULT_FONT_SIZE - 2,
DEFAULT_FONT_SIZE - 2,
DEFAULT_FONT_SIZE - 3);
html.append(css);
}
private static void appendAttachmentsSection(
StringBuilder html,
EmlParser.EmailContent content,
EmlToPdfRequest request,
CustomHtmlSanitizer customHtmlSanitizer) {
html.append("<div class=\"attachment-section\">\n");
int displayedAttachmentCount =
content.getAttachmentCount() > 0
? content.getAttachmentCount()
: content.getAttachments().size();
html.append("<h3>Attachments (").append(displayedAttachmentCount).append(")</h3>\n");
if (!content.getAttachments().isEmpty()) {
for (int i = 0; i < content.getAttachments().size(); i++) {
EmlParser.EmailAttachment attachment = content.getAttachments().get(i);
String embeddedFilename =
attachment.getFilename() != null
? attachment.getFilename()
: ("attachment_" + i);
attachment.setEmbeddedFilename(embeddedFilename);
String sizeStr = GeneralUtils.formatBytes(attachment.getSizeBytes());
String contentType =
attachment.getContentType() != null
&& !attachment.getContentType().isEmpty()
? ", " + escapeHtml(attachment.getContentType())
: "";
String attachmentId = "attachment_" + i;
html.append(
String.format(
"""
<div class="attachment-item" id="%s">
<span class="attachment-icon" data-filename="%s">@</span>
<span class="attachment-name">%s</span>
<span class="attachment-details">(%s%s)</span>
</div>
""",
attachmentId,
escapeHtml(embeddedFilename),
escapeHtml(EmlParser.safeMimeDecode(attachment.getFilename())),
sizeStr,
contentType));
}
}
if (request != null && request.isIncludeAttachments()) {
html.append(
"""
<div class="attachment-info-note">
<p><em>Attachments are embedded in the file.</em></p>
</div>
""");
} else {
html.append(
"""
<div class="attachment-info-note">
<p><em>Attachment information displayed - files not included in PDF.</em></p>
</div>
""");
}
html.append("</div>\n");
}
public static HTMLToPdfRequest createHtmlRequest(EmlToPdfRequest request) {
HTMLToPdfRequest htmlRequest = new HTMLToPdfRequest();
if (request != null) {
htmlRequest.setFileInput(request.getFileInput());
}
htmlRequest.setZoom(Float.parseFloat(DEFAULT_ZOOM));
return htmlRequest;
}
public static String detectMimeType(String filename, String existingMimeType) {
if (existingMimeType != null && !existingMimeType.isEmpty()) {
return existingMimeType;
}
if (filename != null) {
String lowerFilename = filename.toLowerCase();
for (Map.Entry<String, String> entry : EXTENSION_TO_MIME_TYPE.entrySet()) {
if (lowerFilename.endsWith(entry.getKey())) {
return entry.getValue();
}
}
}
return "image/png";
}
public static String decodeUrlEncoded(String encoded) {
try {
return java.net.URLDecoder.decode(encoded, StandardCharsets.UTF_8);
} catch (Exception e) {
return encoded; // Return original if decoding fails
}
}
public static String decodeMimeHeader(String encodedText) {
if (encodedText == null || encodedText.trim().isEmpty()) {
return encodedText;
}
try {
StringBuilder result = new StringBuilder();
Pattern concatenatedPattern =
Pattern.compile(
"(=\\?[^?]+\\?[BbQq]\\?[^?]*\\?=)(\\s*=\\?[^?]+\\?[BbQq]\\?[^?]*\\?=)+");
Matcher concatenatedMatcher = concatenatedPattern.matcher(encodedText);
String processedText =
concatenatedMatcher.replaceAll(
match -> match.group().replaceAll("\\s+(?==\\?)", ""));
Pattern mimePattern = Pattern.compile("=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=");
Matcher matcher = mimePattern.matcher(processedText);
int lastEnd = 0;
while (matcher.find()) {
result.append(processedText, lastEnd, matcher.start());
String charset = matcher.group(1);
String encoding = matcher.group(2).toUpperCase();
String encodedValue = matcher.group(3);
try {
String decodedValue =
switch (encoding) {
case "B" -> {
String cleanBase64 = encodedValue.replaceAll("\\s", "");
byte[] decodedBytes = Base64.getDecoder().decode(cleanBase64);
Charset targetCharset;
try {
targetCharset = Charset.forName(charset);
} catch (Exception e) {
targetCharset = StandardCharsets.UTF_8;
}
yield new String(decodedBytes, targetCharset);
}
case "Q" -> decodeQuotedPrintable(encodedValue, charset);
default -> matcher.group(0); // Return original if unknown encoding
};
result.append(decodedValue);
} catch (RuntimeException e) {
result.append(matcher.group(0)); // Keep original on decode error
}
lastEnd = matcher.end();
}
result.append(processedText.substring(lastEnd));
return result.toString();
} catch (Exception e) {
return encodedText; // Return original on any parsing error
}
}
private static String decodeQuotedPrintable(String encodedText, String charset) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < encodedText.length(); i++) {
char c = encodedText.charAt(i);
switch (c) {
case '=' -> {
if (i + 2 < encodedText.length()) {
String hex = encodedText.substring(i + 1, i + 3);
try {
int value = Integer.parseInt(hex, 16);
result.append((char) value);
i += 2;
} catch (NumberFormatException e) {
result.append(c);
}
} else if (i + 1 == encodedText.length()
|| (i + 2 == encodedText.length()
&& encodedText.charAt(i + 1) == '\n')) {
if (i + 1 < encodedText.length() && encodedText.charAt(i + 1) == '\n') {
i++; // Skip the newline too
}
} else {
result.append(c);
}
}
case '_' -> result.append(' '); // Space encoding in Q encoding
default -> result.append(c);
}
}
byte[] bytes = result.toString().getBytes(StandardCharsets.ISO_8859_1);
try {
Charset targetCharset = Charset.forName(charset);
return new String(bytes, targetCharset);
} catch (Exception e) {
try {
return new String(bytes, StandardCharsets.UTF_8);
} catch (Exception fallbackException) {
return new String(bytes, StandardCharsets.ISO_8859_1);
}
}
}
public static String escapeHtml(String text) {
if (text == null) return "";
return text.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;");
}
public static String sanitizeText(String text, CustomHtmlSanitizer customHtmlSanitizer) {
if (customHtmlSanitizer != null) {
return customHtmlSanitizer.sanitize(text);
} else {
return escapeHtml(text);
}
}
public static String simplifyHtmlContent(String htmlContent) {
String simplified = htmlContent.replaceAll("(?i)<script[^>]*>.*?</script>", "");
simplified = simplified.replaceAll("(?i)<style[^>]*>.*?</style>", "");
return simplified;
}
}
File diff suppressed because it is too large Load Diff
@@ -26,8 +26,8 @@ public class FileToPdf {
HTMLToPdfRequest request,
byte[] fileBytes,
String fileName,
boolean disableSanitize,
TempFileManager tempFileManager)
TempFileManager tempFileManager,
CustomHtmlSanitizer customHtmlSanitizer)
throws IOException, InterruptedException {
try (TempFile tempOutputFile = new TempFile(tempFileManager, ".pdf")) {
@@ -39,14 +39,15 @@ public class FileToPdf {
if (fileName.toLowerCase().endsWith(".html")) {
String sanitizedHtml =
sanitizeHtmlContent(
new String(fileBytes, StandardCharsets.UTF_8), disableSanitize);
new String(fileBytes, StandardCharsets.UTF_8),
customHtmlSanitizer);
Files.write(
tempInputFile.getPath(),
sanitizedHtml.getBytes(StandardCharsets.UTF_8));
} else if (fileName.toLowerCase().endsWith(".zip")) {
Files.write(tempInputFile.getPath(), fileBytes);
sanitizeHtmlFilesInZip(
tempInputFile.getPath(), disableSanitize, tempFileManager);
tempInputFile.getPath(), tempFileManager, customHtmlSanitizer);
} else {
throw ExceptionUtils.createHtmlFileRequiredException();
}
@@ -78,12 +79,15 @@ public class FileToPdf {
} // tempOutputFile auto-closed
}
private static String sanitizeHtmlContent(String htmlContent, boolean disableSanitize) {
return (!disableSanitize) ? CustomHtmlSanitizer.sanitize(htmlContent) : htmlContent;
private static String sanitizeHtmlContent(
String htmlContent, CustomHtmlSanitizer customHtmlSanitizer) {
return customHtmlSanitizer.sanitize(htmlContent);
}
private static void sanitizeHtmlFilesInZip(
Path zipFilePath, boolean disableSanitize, TempFileManager tempFileManager)
Path zipFilePath,
TempFileManager tempFileManager,
CustomHtmlSanitizer customHtmlSanitizer)
throws IOException {
try (TempDirectory tempUnzippedDir = new TempDirectory(tempFileManager)) {
try (ZipInputStream zipIn =
@@ -99,7 +103,8 @@ public class FileToPdf {
|| entry.getName().toLowerCase().endsWith(".htm")) {
String content =
new String(zipIn.readAllBytes(), StandardCharsets.UTF_8);
String sanitizedContent = sanitizeHtmlContent(content, disableSanitize);
String sanitizedContent =
sanitizeHtmlContent(content, customHtmlSanitizer);
Files.write(
filePath, sanitizedContent.getBytes(StandardCharsets.UTF_8));
} else {
@@ -14,8 +14,10 @@ import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternUtils;
@@ -33,6 +35,17 @@ import stirling.software.common.configuration.InstallationPathConfig;
@Slf4j
public class GeneralUtils {
private static final Set<String> DEFAULT_VALID_SCRIPTS =
Set.of("png_to_webp.py", "split_photos.py");
private static final Set<String> DEFAULT_VALID_PIPELINE =
Set.of(
"OCR images.json",
"Prepare-pdfs-for-email.json",
"split-rotate-auto-rename.json");
private static final String DEFAULT_WEBUI_CONFIGS_DIR = "defaultWebUIConfigs";
private static final String PYTHON_SCRIPTS_DIR = "python";
public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
String customTempDir = System.getenv("STIRLING_TEMPFILES_DIRECTORY");
if (customTempDir == null || customTempDir.isEmpty()) {
@@ -442,6 +455,124 @@ public class GeneralUtils {
}
}
/**
* Extracts the default pipeline configurations from the classpath to the installation path.
* Creates directories if needed and copies default JSON files.
*
* <p>Existing files will be overwritten atomically (when supported). In case of unsupported
* atomic moves, falls back to non-atomic replace.
*
* @throws IOException if an I/O error occurs during file operations
*/
public static void extractPipeline() throws IOException {
Path pipelineDir =
Paths.get(InstallationPathConfig.getPipelinePath(), DEFAULT_WEBUI_CONFIGS_DIR);
Files.createDirectories(pipelineDir);
for (String name : DEFAULT_VALID_PIPELINE) {
if (!Paths.get(name).getFileName().toString().equals(name)) {
log.error("Invalid pipeline file name: {}", name);
throw new IllegalArgumentException("Invalid pipeline file name: " + name);
}
Path target = pipelineDir.resolve(name);
ClassPathResource res =
new ClassPathResource(
"static/pipeline/" + DEFAULT_WEBUI_CONFIGS_DIR + "/" + name);
if (!res.exists()) {
log.error("Resource not found: {}", res.getPath());
throw new IOException("Resource not found: " + res.getPath());
}
copyResourceToFile(res, target);
}
}
/**
* Extracts the specified Python script from the classpath to the installation path. Validates
* name and copies file atomically when possible, overwriting existing.
*
* <p>Existing files will be overwritten atomically (when supported).
*
* @param scriptName the name of the script to extract
* @return the path to the extracted script
* @throws IllegalArgumentException if the script name is invalid or not allowed
* @throws IOException if an I/O error occurs
*/
public static Path extractScript(String scriptName) throws IOException {
// Validate input
if (scriptName == null || scriptName.trim().isEmpty()) {
throw new IllegalArgumentException("scriptName must not be null or empty");
}
if (scriptName.contains("..") || scriptName.contains("/")) {
throw new IllegalArgumentException(
"scriptName must not contain path traversal characters");
}
if (!Paths.get(scriptName).getFileName().toString().equals(scriptName)) {
throw new IllegalArgumentException(
"scriptName must not contain path traversal characters");
}
if (!DEFAULT_VALID_SCRIPTS.contains(scriptName)) {
throw new IllegalArgumentException(
"scriptName must be either 'png_to_webp.py' or 'split_photos.py'");
}
Path scriptsDir = Paths.get(InstallationPathConfig.getScriptsPath(), PYTHON_SCRIPTS_DIR);
Files.createDirectories(scriptsDir);
Path target = scriptsDir.resolve(scriptName);
ClassPathResource res =
new ClassPathResource("static/" + PYTHON_SCRIPTS_DIR + "/" + scriptName);
if (!res.exists()) {
log.error("Resource not found: {}", res.getPath());
throw new IOException("Resource not found: " + res.getPath());
}
copyResourceToFile(res, target);
return target;
}
/**
* Copies a resource from the classpath to a specified target file.
*
* @param resource the ClassPathResource to copy
* @param target the target Path where the resource will be copied
* @throws IOException if an I/O error occurs during the copy operation
*/
private static void copyResourceToFile(ClassPathResource resource, Path target)
throws IOException {
Path dir = target.getParent();
Path tmp = Files.createTempFile(dir, target.getFileName().toString(), ".tmp");
try (InputStream in = resource.getInputStream()) {
Files.copy(in, tmp, StandardCopyOption.REPLACE_EXISTING);
try {
Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
log.warn(
"Atomic move not supported, falling back to non-atomic move for {}",
target,
e);
Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING);
}
} catch (FileAlreadyExistsException e) {
log.debug("File already exists at {}, attempting to replace it.", target);
Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING);
} catch (AccessDeniedException e) {
log.error("Access denied while attempting to copy resource to {}", target, e);
throw e;
} catch (FileSystemException e) {
log.error("File system error occurred while copying resource to {}", target, e);
throw e;
} catch (IOException e) {
log.error("Failed to copy resource to {}", target, e);
throw e;
} finally {
try {
Files.deleteIfExists(tmp);
} catch (IOException e) {
log.warn("Failed to delete temporary file {}", tmp, e);
}
}
}
public static boolean isVersionHigher(String currentVersion, String compareVersion) {
if (currentVersion == null || compareVersion == null) {
return false;
@@ -5,8 +5,11 @@ import java.awt.image.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import org.springframework.web.multipart.MultipartFile;
@@ -115,7 +118,36 @@ public class ImageProcessingUtils {
public static BufferedImage loadImageWithExifOrientation(MultipartFile file)
throws IOException {
BufferedImage image = ImageIO.read(file.getInputStream());
BufferedImage image = null;
String filename = file.getOriginalFilename();
if (filename != null && filename.toLowerCase().endsWith(".psd")) {
// For PSD files, try explicit ImageReader
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("PSD");
if (readers.hasNext()) {
ImageReader reader = readers.next();
try (ImageInputStream iis = ImageIO.createImageInputStream(file.getInputStream())) {
reader.setInput(iis);
image = reader.read(0);
} finally {
reader.dispose();
}
}
if (image == null) {
throw new IOException(
"Unable to read image from file: "
+ filename
+ ". Supported PSD formats: RGB/CMYK/Gray 8-32 bit, RLE/ZIP compression");
}
} else {
// For non-PSD files, use standard ImageIO
image = ImageIO.read(file.getInputStream());
}
if (image == null) {
throw new IOException("Unable to read image from file: " + filename);
}
double orientation = extractImageOrientation(file.getInputStream());
return applyOrientation(image, orientation);
}
@@ -0,0 +1,680 @@
package stirling.software.common.util;
import static stirling.software.common.util.AttachmentUtils.setCatalogViewerPreferences;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PageMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.multipart.MultipartFile;
import lombok.Data;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import stirling.software.common.service.CustomPDFDocumentFactory;
@UtilityClass
public class PdfAttachmentHandler {
// Note: This class is designed for EML attachments, not general PDF attachments.
private static final String ATTACHMENT_MARKER = "@";
private static final float ATTACHMENT_ICON_WIDTH = 12f;
private static final float ATTACHMENT_ICON_HEIGHT = 14f;
private static final float ANNOTATION_X_OFFSET = 2f;
private static final float ANNOTATION_Y_OFFSET = 10f;
public static byte[] attachFilesToPdf(
byte[] pdfBytes,
List<EmlParser.EmailAttachment> attachments,
CustomPDFDocumentFactory pdfDocumentFactory)
throws IOException {
if (attachments == null || attachments.isEmpty()) {
return pdfBytes;
}
try (PDDocument document = pdfDocumentFactory.load(pdfBytes);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
List<MultipartFile> multipartAttachments = new ArrayList<>(attachments.size());
for (int i = 0; i < attachments.size(); i++) {
EmlParser.EmailAttachment attachment = attachments.get(i);
if (attachment.getData() != null && attachment.getData().length > 0) {
String embeddedFilename =
attachment.getFilename() != null
? attachment.getFilename()
: ("attachment_" + i);
attachment.setEmbeddedFilename(embeddedFilename);
multipartAttachments.add(createMultipartFile(attachment));
}
}
if (!multipartAttachments.isEmpty()) {
Map<Integer, String> indexToFilenameMap =
addAttachmentsToDocumentWithMapping(
document, multipartAttachments, attachments);
setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS);
addAttachmentAnnotationsToDocumentWithMapping(
document, attachments, indexToFilenameMap);
}
document.save(outputStream);
return outputStream.toByteArray();
} catch (RuntimeException e) {
throw new IOException(
"Invalid PDF structure or processing error: " + e.getMessage(), e);
} catch (Exception e) {
throw new IOException("Error attaching files to PDF: " + e.getMessage(), e);
}
}
private static MultipartFile createMultipartFile(EmlParser.EmailAttachment attachment) {
return new MultipartFile() {
@Override
public @NotNull String getName() {
return "attachment";
}
@Override
public String getOriginalFilename() {
return attachment.getFilename() != null
? attachment.getFilename()
: "attachment_" + System.currentTimeMillis();
}
@Override
public String getContentType() {
return attachment.getContentType() != null
? attachment.getContentType()
: "application/octet-stream";
}
@Override
public boolean isEmpty() {
return attachment.getData() == null || attachment.getData().length == 0;
}
@Override
public long getSize() {
return attachment.getData() != null ? attachment.getData().length : 0;
}
@Override
public byte @NotNull [] getBytes() {
return attachment.getData() != null ? attachment.getData() : new byte[0];
}
@Override
public @NotNull InputStream getInputStream() {
byte[] data = attachment.getData();
return new ByteArrayInputStream(data != null ? data : new byte[0]);
}
@Override
public void transferTo(@NotNull File dest) throws IOException, IllegalStateException {
try (FileOutputStream fos = new FileOutputStream(dest)) {
byte[] data = attachment.getData();
if (data != null) {
fos.write(data);
}
}
}
};
}
private static String ensureUniqueFilename(String filename, Set<String> existingNames) {
if (!existingNames.contains(filename)) {
return filename;
}
String baseName;
String extension = "";
int lastDot = filename.lastIndexOf('.');
if (lastDot > 0) {
baseName = filename.substring(0, lastDot);
extension = filename.substring(lastDot);
} else {
baseName = filename;
}
int counter = 1;
String uniqueName;
do {
uniqueName = baseName + "_" + counter + extension;
counter++;
} while (existingNames.contains(uniqueName));
return uniqueName;
}
private static @NotNull PDRectangle calculateAnnotationRectangle(
PDPage page, float x, float y) {
PDRectangle cropBox = page.getCropBox();
// ISO 32000-1:2008 Section 8.3: PDF coordinate system transforms
int rotation = page.getRotation();
float pdfX = x;
float pdfY = cropBox.getHeight() - y;
switch (rotation) {
case 90 -> {
float temp = pdfX;
pdfX = pdfY;
pdfY = cropBox.getWidth() - temp;
}
case 180 -> {
pdfX = cropBox.getWidth() - pdfX;
pdfY = y;
}
case 270 -> {
float temp = pdfX;
pdfX = cropBox.getHeight() - pdfY;
pdfY = temp;
}
default -> {}
}
float iconHeight = ATTACHMENT_ICON_HEIGHT;
float paddingX = 2.0f;
float paddingY = 2.0f;
PDRectangle rect =
new PDRectangle(
pdfX + ANNOTATION_X_OFFSET + paddingX,
pdfY - iconHeight + ANNOTATION_Y_OFFSET + paddingY,
ATTACHMENT_ICON_WIDTH,
iconHeight);
PDRectangle mediaBox = page.getMediaBox();
if (rect.getLowerLeftX() < mediaBox.getLowerLeftX()
|| rect.getLowerLeftY() < mediaBox.getLowerLeftY()
|| rect.getUpperRightX() > mediaBox.getUpperRightX()
|| rect.getUpperRightY() > mediaBox.getUpperRightY()) {
float adjustedX =
Math.max(
mediaBox.getLowerLeftX(),
Math.min(
rect.getLowerLeftX(),
mediaBox.getUpperRightX() - rect.getWidth()));
float adjustedY =
Math.max(
mediaBox.getLowerLeftY(),
Math.min(
rect.getLowerLeftY(),
mediaBox.getUpperRightY() - rect.getHeight()));
rect = new PDRectangle(adjustedX, adjustedY, rect.getWidth(), rect.getHeight());
}
return rect;
}
public static String processInlineImages(
String htmlContent, EmlParser.EmailContent emailContent) {
if (htmlContent == null || emailContent == null) return htmlContent;
Map<String, EmlParser.EmailAttachment> contentIdMap = new HashMap<>();
for (EmlParser.EmailAttachment attachment : emailContent.getAttachments()) {
if (attachment.isEmbedded()
&& attachment.getContentId() != null
&& attachment.getData() != null) {
contentIdMap.put(attachment.getContentId(), attachment);
}
}
if (contentIdMap.isEmpty()) return htmlContent;
Pattern cidPattern =
Pattern.compile(
"(?i)<img[^>]*\\ssrc\\s*=\\s*['\"]cid:([^'\"]+)['\"][^>]*>",
Pattern.CASE_INSENSITIVE);
Matcher matcher = cidPattern.matcher(htmlContent);
StringBuilder result = new StringBuilder();
while (matcher.find()) {
String contentId = matcher.group(1);
EmlParser.EmailAttachment attachment = contentIdMap.get(contentId);
if (attachment != null && attachment.getData() != null) {
String mimeType =
EmlProcessingUtils.detectMimeType(
attachment.getFilename(), attachment.getContentType());
String base64Data = Base64.getEncoder().encodeToString(attachment.getData());
String dataUri = "data:" + mimeType + ";base64," + base64Data;
String replacement =
matcher.group(0).replaceFirst("cid:" + Pattern.quote(contentId), dataUri);
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
} else {
matcher.appendReplacement(result, Matcher.quoteReplacement(matcher.group(0)));
}
}
matcher.appendTail(result);
return result.toString();
}
public static String formatEmailDate(Date date) {
if (date == null) return "";
SimpleDateFormat formatter =
new SimpleDateFormat("EEE, MMM d, yyyy 'at' h:mm a z", Locale.ENGLISH);
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
return formatter.format(date);
}
@Data
public static class MarkerPosition {
private int pageIndex;
private float x;
private float y;
private String character;
private String filename;
public MarkerPosition(int pageIndex, float x, float y, String character, String filename) {
this.pageIndex = pageIndex;
this.x = x;
this.y = y;
this.character = character;
this.filename = filename;
}
}
public static class AttachmentMarkerPositionFinder extends PDFTextStripper {
@Getter private final List<MarkerPosition> positions = new ArrayList<>();
private int currentPageIndex;
protected boolean sortByPosition;
private boolean isInAttachmentSection;
private boolean attachmentSectionFound;
private final StringBuilder currentText = new StringBuilder();
private static final Pattern ATTACHMENT_SECTION_PATTERN =
Pattern.compile("attachments\\s*\\(\\d+\\)", Pattern.CASE_INSENSITIVE);
private static final Pattern FILENAME_PATTERN =
Pattern.compile("@\\s*([^\\s\\(]+(?:\\.[a-zA-Z0-9]+)?)");
public AttachmentMarkerPositionFinder() {
super();
this.currentPageIndex = 0;
this.sortByPosition = false; // Disable sorting to preserve document order
this.isInAttachmentSection = false;
this.attachmentSectionFound = false;
}
@Override
public String getText(PDDocument document) throws IOException {
super.getText(document);
if (sortByPosition) {
positions.sort(
(a, b) -> {
int pageCompare = Integer.compare(a.getPageIndex(), b.getPageIndex());
if (pageCompare != 0) return pageCompare;
return Float.compare(
b.getY(), a.getY()); // Descending Y per PDF coordinate system
});
}
return ""; // Return empty string as we only need positions
}
@Override
protected void startPage(PDPage page) throws IOException {
super.startPage(page);
}
@Override
protected void endPage(PDPage page) throws IOException {
currentPageIndex++;
super.endPage(page);
}
@Override
protected void writeString(String string, List<TextPosition> textPositions)
throws IOException {
String lowerString = string.toLowerCase();
if (ATTACHMENT_SECTION_PATTERN.matcher(lowerString).find()) {
isInAttachmentSection = true;
attachmentSectionFound = true;
}
if (isInAttachmentSection
&& (lowerString.contains("</body>")
|| lowerString.contains("</html>")
|| (attachmentSectionFound
&& lowerString.trim().isEmpty()
&& string.length() > 50))) {
isInAttachmentSection = false;
}
if (isInAttachmentSection) {
currentText.append(string);
for (int i = 0; (i = string.indexOf(ATTACHMENT_MARKER, i)) != -1; i++) {
if (i < textPositions.size()) {
TextPosition textPosition = textPositions.get(i);
String filename = extractFilenameAfterMarker(string, i);
MarkerPosition position =
new MarkerPosition(
currentPageIndex,
textPosition.getXDirAdj(),
textPosition.getYDirAdj(),
ATTACHMENT_MARKER,
filename);
positions.add(position);
}
}
}
super.writeString(string, textPositions);
}
@Override
public void setSortByPosition(boolean sortByPosition) {
this.sortByPosition = sortByPosition;
}
private String extractFilenameAfterMarker(String text, int markerIndex) {
String afterMarker = text.substring(markerIndex + 1);
Matcher matcher = FILENAME_PATTERN.matcher("@" + afterMarker);
if (matcher.find()) {
return matcher.group(1);
}
String[] parts = afterMarker.split("[\\s\\(\\)]+");
for (String part : parts) {
part = part.trim();
if (part.length() > 3 && part.contains(".")) {
return part;
}
}
return null;
}
}
private static Map<Integer, String> addAttachmentsToDocumentWithMapping(
PDDocument document,
List<MultipartFile> attachments,
List<EmlParser.EmailAttachment> originalAttachments)
throws IOException {
PDDocumentCatalog catalog = document.getDocumentCatalog();
if (catalog == null) {
throw new IOException("PDF document catalog is not accessible");
}
PDDocumentNameDictionary documentNames = catalog.getNames();
if (documentNames == null) {
documentNames = new PDDocumentNameDictionary(catalog);
catalog.setNames(documentNames);
}
PDEmbeddedFilesNameTreeNode embeddedFilesTree = documentNames.getEmbeddedFiles();
if (embeddedFilesTree == null) {
embeddedFilesTree = new PDEmbeddedFilesNameTreeNode();
documentNames.setEmbeddedFiles(embeddedFilesTree);
}
Map<String, PDComplexFileSpecification> existingNames = embeddedFilesTree.getNames();
if (existingNames == null) {
existingNames = new HashMap<>();
}
Map<Integer, String> indexToFilenameMap = new HashMap<>();
for (int i = 0; i < attachments.size(); i++) {
MultipartFile attachment = attachments.get(i);
String filename = attachment.getOriginalFilename();
if (filename == null || filename.trim().isEmpty()) {
filename = "attachment_" + i;
}
String normalizedFilename =
isAscii(filename)
? filename
: java.text.Normalizer.normalize(
filename, java.text.Normalizer.Form.NFC);
String uniqueFilename =
ensureUniqueFilename(normalizedFilename, existingNames.keySet());
indexToFilenameMap.put(i, uniqueFilename);
PDEmbeddedFile embeddedFile = new PDEmbeddedFile(document, attachment.getInputStream());
embeddedFile.setSize((int) attachment.getSize());
GregorianCalendar currentTime = new GregorianCalendar();
embeddedFile.setCreationDate(currentTime);
embeddedFile.setModDate(currentTime);
String contentType = attachment.getContentType();
if (contentType != null && !contentType.trim().isEmpty()) {
embeddedFile.setSubtype(contentType);
}
PDComplexFileSpecification fileSpecification = new PDComplexFileSpecification();
fileSpecification.setFile(uniqueFilename);
fileSpecification.setFileUnicode(uniqueFilename);
fileSpecification.setEmbeddedFile(embeddedFile);
fileSpecification.setEmbeddedFileUnicode(embeddedFile);
existingNames.put(uniqueFilename, fileSpecification);
}
embeddedFilesTree.setNames(existingNames);
documentNames.setEmbeddedFiles(embeddedFilesTree);
catalog.setNames(documentNames);
return indexToFilenameMap;
}
private static void addAttachmentAnnotationsToDocumentWithMapping(
PDDocument document,
List<EmlParser.EmailAttachment> attachments,
Map<Integer, String> indexToFilenameMap)
throws IOException {
if (document.getNumberOfPages() == 0 || attachments == null || attachments.isEmpty()) {
return;
}
AttachmentMarkerPositionFinder finder = new AttachmentMarkerPositionFinder();
finder.setSortByPosition(false); // Keep document order to maintain pairing
finder.getText(document);
List<MarkerPosition> markerPositions = finder.getPositions();
int annotationsToAdd = Math.min(markerPositions.size(), attachments.size());
for (int i = 0; i < annotationsToAdd; i++) {
MarkerPosition position = markerPositions.get(i);
String filenameNearMarker = position.getFilename();
EmlParser.EmailAttachment matchingAttachment =
findAttachmentByFilename(attachments, filenameNearMarker);
if (matchingAttachment != null) {
String embeddedFilename =
findEmbeddedFilenameForAttachment(matchingAttachment, indexToFilenameMap);
if (embeddedFilename != null) {
PDPage page = document.getPage(position.getPageIndex());
addAttachmentAnnotationToPageWithMapping(
document,
page,
matchingAttachment,
embeddedFilename,
position.getX(),
position.getY(),
i);
} else {
// No embedded filename found for attachment
}
} else {
// No matching attachment found for filename near marker
}
}
}
private static EmlParser.EmailAttachment findAttachmentByFilename(
List<EmlParser.EmailAttachment> attachments, String targetFilename) {
if (targetFilename == null || targetFilename.trim().isEmpty()) {
return null;
}
String normalizedTarget = normalizeFilename(targetFilename);
// First try exact match
for (EmlParser.EmailAttachment attachment : attachments) {
if (attachment.getFilename() != null) {
String normalizedAttachment = normalizeFilename(attachment.getFilename());
if (normalizedAttachment.equals(normalizedTarget)) {
return attachment;
}
}
}
// Then try contains match
for (EmlParser.EmailAttachment attachment : attachments) {
if (attachment.getFilename() != null) {
String normalizedAttachment = normalizeFilename(attachment.getFilename());
if (normalizedAttachment.contains(normalizedTarget)
|| normalizedTarget.contains(normalizedAttachment)) {
return attachment;
}
}
}
return null;
}
private static String findEmbeddedFilenameForAttachment(
EmlParser.EmailAttachment attachment, Map<Integer, String> indexToFilenameMap) {
String attachmentFilename = attachment.getFilename();
if (attachmentFilename == null) {
return null;
}
for (Map.Entry<Integer, String> entry : indexToFilenameMap.entrySet()) {
String embeddedFilename = entry.getValue();
if (embeddedFilename != null
&& (embeddedFilename.equals(attachmentFilename)
|| embeddedFilename.contains(attachmentFilename)
|| attachmentFilename.contains(embeddedFilename))) {
return embeddedFilename;
}
}
return null;
}
private static String normalizeFilename(String filename) {
if (filename == null) return "";
return filename.toLowerCase()
.trim()
.replaceAll("\\s+", " ")
.replaceAll("[^a-zA-Z0-9._-]", "");
}
private static void addAttachmentAnnotationToPageWithMapping(
PDDocument document,
PDPage page,
EmlParser.EmailAttachment attachment,
String embeddedFilename,
float x,
float y,
int attachmentIndex)
throws IOException {
PDAnnotationFileAttachment fileAnnotation = new PDAnnotationFileAttachment();
PDRectangle rect = calculateAnnotationRectangle(page, x, y);
fileAnnotation.setRectangle(rect);
fileAnnotation.setPrinted(false);
fileAnnotation.setHidden(false);
fileAnnotation.setNoView(false);
fileAnnotation.setNoZoom(true);
fileAnnotation.setNoRotate(true);
try {
PDAppearanceDictionary appearance = new PDAppearanceDictionary();
PDAppearanceStream normalAppearance = new PDAppearanceStream(document);
normalAppearance.setBBox(new PDRectangle(0, 0, rect.getWidth(), rect.getHeight()));
appearance.setNormalAppearance(normalAppearance);
fileAnnotation.setAppearance(appearance);
} catch (RuntimeException e) {
fileAnnotation.setAppearance(null);
}
PDEmbeddedFilesNameTreeNode efTree =
document.getDocumentCatalog().getNames().getEmbeddedFiles();
if (efTree != null) {
Map<String, PDComplexFileSpecification> efMap = efTree.getNames();
if (efMap != null) {
PDComplexFileSpecification fileSpec = efMap.get(embeddedFilename);
if (fileSpec != null) {
fileAnnotation.setFile(fileSpec);
} else {
// Could not find embedded file
}
}
}
fileAnnotation.setContents(
"Attachment " + (attachmentIndex + 1) + ": " + attachment.getFilename());
fileAnnotation.setAnnotationName(
"EmbeddedFile_" + attachmentIndex + "_" + embeddedFilename);
page.getAnnotations().add(fileAnnotation);
}
private static boolean isAscii(String str) {
if (str == null) return true;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) > 127) {
return false;
}
}
return true;
}
}
@@ -35,6 +35,7 @@ import io.github.pixee.security.Filenames;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.CustomPDFDocumentFactory;
@Slf4j
@@ -145,13 +146,18 @@ public class PdfUtils {
throws IOException, Exception {
// Validate and limit DPI to prevent excessive memory usage
final int MAX_SAFE_DPI = 500; // Maximum safe DPI to prevent memory issues
if (DPI > MAX_SAFE_DPI) {
int maxSafeDpi = 500; // Default maximum safe DPI
ApplicationProperties properties =
ApplicationContextProvider.getBean(ApplicationProperties.class);
if (properties != null && properties.getSystem() != null) {
maxSafeDpi = properties.getSystem().getMaxDPI();
}
if (DPI > maxSafeDpi) {
throw ExceptionUtils.createIllegalArgumentException(
"error.dpiExceedsLimit",
"DPI value {0} exceeds maximum safe limit of {1}. High DPI values can cause memory issues and crashes. Please use a lower DPI value.",
DPI,
MAX_SAFE_DPI);
maxSafeDpi);
}
try (PDDocument document = pdfDocumentFactory.load(inputStream)) {
@@ -14,8 +14,10 @@ public class RequestUriUtils {
|| requestURI.startsWith(contextPath + "/images/")
|| requestURI.startsWith(contextPath + "/public/")
|| requestURI.startsWith(contextPath + "/pdfjs/")
|| requestURI.startsWith(contextPath + "/pdfjs-legacy/")
|| requestURI.startsWith(contextPath + "/login")
|| requestURI.startsWith(contextPath + "/error")
|| requestURI.startsWith(contextPath + "/favicon")
|| requestURI.endsWith(".svg")
|| requestURI.endsWith(".png")
|| requestURI.endsWith(".ico")
@@ -7,24 +7,19 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeEach;
import java.util.Arrays;
import java.util.function.Supplier;
import org.aspectj.lang.ProceedingJoinPoint;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.ResponseEntity;
@@ -45,62 +40,44 @@ class AutoJobPostMappingIntegrationTest {
private AutoJobAspect autoJobAspect;
@Mock
private JobExecutorService jobExecutorService;
@Mock private JobExecutorService jobExecutorService;
@Mock
private HttpServletRequest request;
@Mock private HttpServletRequest request;
@Mock
private FileOrUploadService fileOrUploadService;
@Mock private FileOrUploadService fileOrUploadService;
@Mock
private FileStorage fileStorage;
@Mock private FileStorage fileStorage;
@Mock private ResourceMonitor resourceMonitor;
@Mock
private ResourceMonitor resourceMonitor;
@Mock
private JobQueue jobQueue;
@Mock private JobQueue jobQueue;
@BeforeEach
void setUp() {
autoJobAspect = new AutoJobAspect(
jobExecutorService,
request,
fileOrUploadService,
fileStorage
);
autoJobAspect =
new AutoJobAspect(jobExecutorService, request, fileOrUploadService, fileStorage);
}
@Mock
private ProceedingJoinPoint joinPoint;
@Mock private ProceedingJoinPoint joinPoint;
@Mock
private AutoJobPostMapping autoJobPostMapping;
@Mock private AutoJobPostMapping autoJobPostMapping;
@Captor
private ArgumentCaptor<Supplier<Object>> workCaptor;
@Captor private ArgumentCaptor<Supplier<Object>> workCaptor;
@Captor
private ArgumentCaptor<Boolean> asyncCaptor;
@Captor private ArgumentCaptor<Boolean> asyncCaptor;
@Captor
private ArgumentCaptor<Long> timeoutCaptor;
@Captor private ArgumentCaptor<Long> timeoutCaptor;
@Captor
private ArgumentCaptor<Boolean> queueableCaptor;
@Captor private ArgumentCaptor<Boolean> queueableCaptor;
@Captor
private ArgumentCaptor<Integer> resourceWeightCaptor;
@Captor private ArgumentCaptor<Integer> resourceWeightCaptor;
@Test
void shouldExecuteWithCustomParameters() throws Throwable {
// Given
PDFFile pdfFile = new PDFFile();
pdfFile.setFileId("test-file-id");
Object[] args = new Object[] { pdfFile };
Object[] args = new Object[] {pdfFile};
when(joinPoint.getArgs()).thenReturn(args);
when(request.getParameter("async")).thenReturn("true");
@@ -113,9 +90,8 @@ class AutoJobPostMappingIntegrationTest {
MultipartFile mockFile = mock(MultipartFile.class);
when(fileStorage.retrieveFile("test-file-id")).thenReturn(mockFile);
when(jobExecutorService.runJobGeneric(
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
.thenReturn(ResponseEntity.ok("success"));
// When
@@ -124,12 +100,13 @@ class AutoJobPostMappingIntegrationTest {
// Then
assertEquals(ResponseEntity.ok("success"), result);
verify(jobExecutorService).runJobGeneric(
asyncCaptor.capture(),
workCaptor.capture(),
timeoutCaptor.capture(),
queueableCaptor.capture(),
resourceWeightCaptor.capture());
verify(jobExecutorService)
.runJobGeneric(
asyncCaptor.capture(),
workCaptor.capture(),
timeoutCaptor.capture(),
queueableCaptor.capture(),
resourceWeightCaptor.capture());
assertTrue(asyncCaptor.getValue(), "Async should be true");
assertEquals(60000L, timeoutCaptor.getValue(), "Timeout should be 60000ms");
@@ -158,11 +135,12 @@ class AutoJobPostMappingIntegrationTest {
// Mock jobExecutorService to execute the work immediately
when(jobExecutorService.runJobGeneric(
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
.thenAnswer(invocation -> {
Supplier<Object> work = invocation.getArgument(1);
return work.get();
});
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
.thenAnswer(
invocation -> {
Supplier<Object> work = invocation.getArgument(1);
return work.get();
});
// When
Object result = autoJobAspect.wrapWithJobExecution(joinPoint, autoJobPostMapping);
@@ -179,7 +157,7 @@ class AutoJobPostMappingIntegrationTest {
// Given
PDFFile pdfFile = new PDFFile();
pdfFile.setFileInput(mock(MultipartFile.class));
Object[] args = new Object[] { pdfFile };
Object[] args = new Object[] {pdfFile};
when(joinPoint.getArgs()).thenReturn(args);
when(request.getParameter("async")).thenReturn("true");
@@ -190,14 +168,16 @@ class AutoJobPostMappingIntegrationTest {
// Mock job executor to return a successful response
when(jobExecutorService.runJobGeneric(
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
.thenReturn(ResponseEntity.ok("success"));
// When
autoJobAspect.wrapWithJobExecution(joinPoint, autoJobPostMapping);
// Then
assertEquals("stored-file-id", pdfFile.getFileId(),
assertEquals(
"stored-file-id",
pdfFile.getFileId(),
"FileId should be set to the stored file id");
assertNotNull(pdfFile.getFileInput(), "FileInput should be replaced with persistent file");
@@ -0,0 +1,59 @@
package stirling.software.common.model;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
import stirling.software.common.configuration.InstallationPathConfig;
class ApplicationPropertiesDynamicYamlPropertySourceTest {
@Test
void loads_yaml_into_environment() throws Exception {
// YAML-Config in Temp-Datei schreiben
String yaml =
""
+ "ui:\n"
+ " appName: \"My App\"\n"
+ "system:\n"
+ " enableAnalytics: true\n";
Path tmp = Files.createTempFile("spdf-settings-", ".yml");
Files.writeString(tmp, yaml);
// Pfad per statischem Mock liefern
try (MockedStatic<InstallationPathConfig> mocked =
Mockito.mockStatic(InstallationPathConfig.class)) {
mocked.when(InstallationPathConfig::getSettingsPath).thenReturn(tmp.toString());
ConfigurableEnvironment env = new StandardEnvironment();
ApplicationProperties props = new ApplicationProperties();
props.dynamicYamlPropertySource(env); // fügt PropertySource an erster Stelle ein
assertEquals("My App", env.getProperty("ui.appName"));
assertEquals("true", env.getProperty("system.enableAnalytics"));
}
}
@Test
void throws_when_settings_file_missing() throws Exception {
String missing = "/path/does/not/exist/spdf.yml";
try (MockedStatic<InstallationPathConfig> mocked =
Mockito.mockStatic(InstallationPathConfig.class)) {
mocked.when(InstallationPathConfig::getSettingsPath).thenReturn(missing);
ConfigurableEnvironment env = new StandardEnvironment();
ApplicationProperties props = new ApplicationProperties();
assertThrows(IOException.class, () -> props.dynamicYamlPropertySource(env));
}
}
}
@@ -0,0 +1,248 @@
package stirling.software.common.model;
import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.ApplicationProperties.Driver;
import stirling.software.common.model.ApplicationProperties.Premium;
import stirling.software.common.model.ApplicationProperties.Security;
import stirling.software.common.model.exception.UnsupportedProviderException;
class ApplicationPropertiesLogicTest {
@Test
void system_isAnalyticsEnabled_null_false_true() {
ApplicationProperties.System sys = new ApplicationProperties.System();
sys.setEnableAnalytics(null);
assertFalse(sys.isAnalyticsEnabled());
sys.setEnableAnalytics(Boolean.FALSE);
assertFalse(sys.isAnalyticsEnabled());
sys.setEnableAnalytics(Boolean.TRUE);
assertTrue(sys.isAnalyticsEnabled());
}
@Test
void tempFileManagement_defaults_and_overrides() {
ApplicationProperties.TempFileManagement tfm =
new ApplicationProperties.TempFileManagement();
String expectedBase =
java.lang.System.getProperty("java.io.tmpdir").replaceAll("/+$", "")
+ "/stirling-pdf";
assertEquals(expectedBase, tfm.getBaseTmpDir());
String expectedLibre = expectedBase + "/libreoffice";
assertEquals(expectedLibre, tfm.getLibreofficeDir());
tfm.setBaseTmpDir("/custom/base");
assertEquals("/custom/base", tfm.getBaseTmpDir());
tfm.setLibreofficeDir("/opt/libre");
assertEquals("/opt/libre", tfm.getLibreofficeDir());
}
@Test
void oauth2_scope_parsing_and_validity() {
Security.OAUTH2 oauth2 = new Security.OAUTH2();
oauth2.setIssuer("https://issuer");
oauth2.setClientId("client");
oauth2.setClientSecret("secret");
oauth2.setUseAsUsername("email");
oauth2.setScopes("openid, profile ,email");
assertTrue(oauth2.isSettingsValid());
}
@Test
void security_login_method_flags() {
Security sec = new Security();
sec.getOauth2().setEnabled(true);
sec.getSaml2().setEnabled(true);
assertTrue(sec.isUserPass());
assertTrue(sec.isOauth2Active());
assertTrue(sec.isSaml2Active());
sec.setLoginMethod(Security.LoginMethods.NORMAL.toString());
assertTrue(sec.isUserPass());
assertFalse(sec.isOauth2Active());
assertFalse(sec.isSaml2Active());
}
@Test
void security_isAltLogin_reflects_oauth2_or_saml2() {
Security sec = new Security();
assertFalse(sec.isAltLogin());
sec.getOauth2().setEnabled(true);
sec.getSaml2().setEnabled(false);
assertTrue(sec.isAltLogin());
sec.getOauth2().setEnabled(false);
sec.getSaml2().setEnabled(true);
assertTrue(sec.isAltLogin());
sec.getOauth2().setEnabled(true);
sec.getSaml2().setEnabled(true);
assertTrue(sec.isAltLogin());
}
@Test
void oauth2_client_provider_mapping_and_unsupported() throws UnsupportedProviderException {
Security.OAUTH2.Client client = new Security.OAUTH2.Client();
assertNotNull(client.get("google"));
assertNotNull(client.get("github"));
assertNotNull(client.get("keycloak"));
UnsupportedProviderException ex =
assertThrows(UnsupportedProviderException.class, () -> client.get("unknown"));
assertTrue(ex.getMessage().toLowerCase().contains("not supported"));
}
@Test
void premium_google_drive_getters_return_empty_string_on_null_or_blank() {
Premium.ProFeatures.GoogleDrive gd = new Premium.ProFeatures.GoogleDrive();
assertEquals("", gd.getClientId());
assertEquals("", gd.getApiKey());
assertEquals("", gd.getAppId());
gd.setClientId(" id ");
gd.setApiKey(" key ");
gd.setAppId(" app ");
assertEquals(" id ", gd.getClientId());
assertEquals(" key ", gd.getApiKey());
assertEquals(" app ", gd.getAppId());
}
@Test
void ui_getters_return_null_for_blank() {
ApplicationProperties.Ui ui = new ApplicationProperties.Ui();
ui.setAppName(" ");
ui.setHomeDescription("");
ui.setAppNameNavbar(null);
assertNull(ui.getAppName());
assertNull(ui.getHomeDescription());
assertNull(ui.getAppNameNavbar());
ui.setAppName("Stirling-PDF");
ui.setHomeDescription("Home");
ui.setAppNameNavbar("Nav");
assertEquals("Stirling-PDF", ui.getAppName());
assertEquals("Home", ui.getHomeDescription());
assertEquals("Nav", ui.getAppNameNavbar());
}
@Test
void driver_toString_contains_driver_name() {
assertTrue(Driver.H2.toString().contains("h2"));
assertTrue(Driver.POSTGRESQL.toString().contains("postgresql"));
}
@Test
void session_limits_and_timeouts_have_reasonable_defaults() {
ApplicationProperties.ProcessExecutor pe = new ApplicationProperties.ProcessExecutor();
ApplicationProperties.ProcessExecutor.SessionLimit s = pe.getSessionLimit();
assertEquals(2, s.getQpdfSessionLimit());
assertEquals(1, s.getTesseractSessionLimit());
assertEquals(1, s.getLibreOfficeSessionLimit());
assertEquals(1, s.getPdfToHtmlSessionLimit());
assertEquals(8, s.getPythonOpenCvSessionLimit());
assertEquals(16, s.getWeasyPrintSessionLimit());
assertEquals(1, s.getInstallAppSessionLimit());
assertEquals(1, s.getCalibreSessionLimit());
assertEquals(8, s.getGhostscriptSessionLimit());
assertEquals(2, s.getOcrMyPdfSessionLimit());
ApplicationProperties.ProcessExecutor.TimeoutMinutes t = pe.getTimeoutMinutes();
assertEquals(30, t.getTesseractTimeoutMinutes());
assertEquals(30, t.getQpdfTimeoutMinutes());
assertEquals(30, t.getLibreOfficeTimeoutMinutes());
assertEquals(20, t.getPdfToHtmlTimeoutMinutes());
assertEquals(30, t.getPythonOpenCvTimeoutMinutes());
assertEquals(30, t.getWeasyPrintTimeoutMinutes());
assertEquals(60, t.getInstallAppTimeoutMinutes());
assertEquals(30, t.getCalibreTimeoutMinutes());
assertEquals(30, t.getGhostscriptTimeoutMinutes());
assertEquals(30, t.getOcrMyPdfTimeoutMinutes());
}
@Deprecated
@Test
void enterprise_metadata_defaults() {
ApplicationProperties.EnterpriseEdition ee = new ApplicationProperties.EnterpriseEdition();
ApplicationProperties.EnterpriseEdition.CustomMetadata eMeta = ee.getCustomMetadata();
eMeta.setCreator(" ");
eMeta.setProducer(null);
assertEquals("Stirling-PDF", eMeta.getCreator());
assertEquals("Stirling-PDF", eMeta.getProducer());
}
@Test
void premium_metadata_defaults() {
Premium.ProFeatures pf = new Premium.ProFeatures();
Premium.ProFeatures.CustomMetadata pMeta = pf.getCustomMetadata();
pMeta.setCreator("");
pMeta.setProducer("");
assertEquals("Stirling-PDF", pMeta.getCreator());
assertEquals("Stirling-PDF", pMeta.getProducer());
}
@Test
void premium_metadata_awesome() {
Premium.ProFeatures pf = new Premium.ProFeatures();
Premium.ProFeatures.CustomMetadata pMeta = pf.getCustomMetadata();
pMeta.setCreator("Awesome PDF Tool");
pMeta.setProducer("Awesome PDF Tool");
assertEquals("Awesome PDF Tool", pMeta.getCreator());
assertEquals("Awesome PDF Tool", pMeta.getProducer());
}
@Test
void string_isValid_handles_null_empty_blank_and_trimmed() {
ApplicationProperties.Security.OAUTH2 oauth2 = new ApplicationProperties.Security.OAUTH2();
assertFalse(oauth2.isValid((String) null, "issuer"));
assertFalse(oauth2.isValid("", "issuer"));
assertFalse(oauth2.isValid(" ", "issuer"));
assertTrue(oauth2.isValid("x", "issuer"));
assertTrue(oauth2.isValid(" x ", "issuer")); // trimmt intern
}
@Test
void collection_isValid_handles_null_and_empty() {
ApplicationProperties.Security.OAUTH2 oauth2 = new ApplicationProperties.Security.OAUTH2();
Collection<String> nullColl = null;
Collection<String> empty = List.of();
assertFalse(oauth2.isValid(nullColl, "scopes"));
assertFalse(oauth2.isValid(empty, "scopes"));
}
@Test
void collection_isValid_true_when_non_empty_even_if_element_is_blank() {
ApplicationProperties.Security.OAUTH2 oauth2 = new ApplicationProperties.Security.OAUTH2();
// Aktuelles Verhalten: prüft NUR !isEmpty(), nicht Inhalt
Collection<String> oneBlank = new ArrayList<>();
oneBlank.add(" ");
assertTrue(
oauth2.isValid(oneBlank, "scopes"),
"Dokumentiert aktuelles Verhalten: nicht-leere Liste gilt als gültig, auch wenn Element leer/blank ist");
}
}
@@ -0,0 +1,80 @@
package stirling.software.common.model;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
class ApplicationPropertiesSaml2HttpTest {
@Test
void idpMetadataUri_http_is_resolved_via_mockwebserver() throws Exception {
try (MockWebServer server = new MockWebServer()) {
server.enqueue(
new MockResponse()
.setResponseCode(200)
.addHeader("Content-Type", "application/xml")
.setBody("<EntityDescriptor/>"));
server.start();
String url = server.url("/meta").toString();
var s = new ApplicationProperties.Security.SAML2();
s.setIdpMetadataUri(url);
try (InputStream in = s.getIdpMetadataUri()) {
String body = new String(in.readAllBytes(), StandardCharsets.UTF_8);
assertTrue(body.contains("EntityDescriptor"));
}
}
}
@Test
void idpMetadataUri_invalidUri_triggers_catch_and_throwsIOException() {
// Ungültige URI -> new URI(...) wirft URISyntaxException -> catch -> IOException
var s = new ApplicationProperties.Security.SAML2();
s.setIdpMetadataUri("http:##invalid uri"); // absichtlich kaputt (Leerzeichen + ##)
assertThrows(IOException.class, s::getIdpMetadataUri);
}
@Test
void spCert_else_branch_returns_FileSystemResource_for_filesystem_path() throws Exception {
var s = new ApplicationProperties.Security.SAML2();
// temporäre Datei simuliert "Filesystem"-Pfad (-> else-Zweig)
Path tmp = Files.createTempFile("spdf-spcert-", ".crt");
Files.writeString(tmp, "CERT");
s.setSpCert(tmp.toString());
Resource r = s.getSpCert();
assertNotNull(r);
assertTrue(r instanceof FileSystemResource, "Expected FileSystemResource for FS path");
assertTrue(r.exists(), "Temp file should exist");
}
@Test
void idpCert_else_branch_returns_FileSystemResource_even_if_missing() {
var s = new ApplicationProperties.Security.SAML2();
// bewusst nicht existierender Pfad -> else-Zweig wird trotzdem genommen
String missing = "/this/path/does/not/exist/idp.crt";
s.setIdpCert(missing);
Resource r = s.getIdpCert();
assertNotNull(r);
assertTrue(r instanceof FileSystemResource, "Expected FileSystemResource for FS path");
assertFalse(r.exists(), "Resource should not exist for missing file");
}
}
@@ -0,0 +1,55 @@
package stirling.software.common.model;
import static org.junit.jupiter.api.Assertions.*;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
class ApplicationPropertiesSaml2ResourceTest {
@Test
void idpMetadataUri_classpath_is_resolved() throws Exception {
var s = new ApplicationProperties.Security.SAML2();
s.setIdpMetadataUri("classpath:saml/dummy.txt");
try (InputStream in = s.getIdpMetadataUri()) {
assertNotNull(in, "Classpath InputStream should not be null");
String txt = new String(in.readAllBytes(), StandardCharsets.UTF_8);
assertTrue(txt.contains("ok"));
}
}
@Test
void spCert_idpCert_privateKey_null_classpath_and_filesystem() throws Exception {
var s = new ApplicationProperties.Security.SAML2();
s.setSpCert(null);
s.setIdpCert(null);
s.setPrivateKey(null);
assertNull(s.getSpCert());
assertNull(s.getIdpCert());
assertNull(s.getPrivateKey());
s.setSpCert("classpath:saml/dummy.txt");
s.setIdpCert("classpath:saml/dummy.txt");
s.setPrivateKey("classpath:saml/dummy.txt");
Resource sp = s.getSpCert();
Resource idp = s.getIdpCert();
Resource pk = s.getPrivateKey();
assertTrue(sp.exists());
assertTrue(idp.exists());
assertTrue(pk.exists());
Path tmp = Files.createTempFile("spdf-key-", ".pem");
Files.writeString(tmp, "KEY");
s.setPrivateKey(tmp.toString());
Resource pkFs = s.getPrivateKey();
assertNotNull(pkFs);
assertTrue(pkFs.exists());
}
}
@@ -1,10 +1,9 @@
package stirling.software.common.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.AdditionalAnswers.*;
import static org.mockito.Mockito.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -21,14 +20,11 @@ import org.springframework.web.multipart.MultipartFile;
class FileStorageTest {
@TempDir
Path tempDir;
@TempDir Path tempDir;
@Mock
private FileOrUploadService fileOrUploadService;
@Mock private FileOrUploadService fileOrUploadService;
@InjectMocks
private FileStorage fileStorage;
@InjectMocks private FileStorage fileStorage;
private MultipartFile mockFile;
@@ -50,11 +46,14 @@ class FileStorageTest {
when(mockFile.getBytes()).thenReturn(fileContent);
// Set up mock to handle transferTo by writing the file
doAnswer(invocation -> {
java.io.File file = invocation.getArgument(0);
Files.write(file.toPath(), fileContent);
return null;
}).when(mockFile).transferTo(any(java.io.File.class));
doAnswer(
invocation -> {
java.io.File file = invocation.getArgument(0);
Files.write(file.toPath(), fileContent);
return null;
})
.when(mockFile)
.transferTo(any(java.io.File.class));
// Act
String fileId = fileStorage.storeFile(mockFile);
@@ -90,7 +89,7 @@ class FileStorageTest {
MultipartFile expectedFile = mock(MultipartFile.class);
when(fileOrUploadService.toMockMultipartFile(eq(fileId), eq(fileContent)))
.thenReturn(expectedFile);
.thenReturn(expectedFile);
// Act
MultipartFile result = fileStorage.retrieveFile(fileId);
@@ -4,14 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,11 +25,9 @@ import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import jakarta.servlet.http.HttpServletRequest;
import stirling.software.common.model.job.JobProgress;
import stirling.software.common.model.job.JobResponse;
@ExtendWith(MockitoExtension.class)
@@ -42,36 +35,31 @@ class JobExecutorServiceTest {
private JobExecutorService jobExecutorService;
@Mock
private TaskManager taskManager;
@Mock private TaskManager taskManager;
@Mock
private FileStorage fileStorage;
@Mock private FileStorage fileStorage;
@Mock
private HttpServletRequest request;
@Mock private HttpServletRequest request;
@Mock
private ResourceMonitor resourceMonitor;
@Mock private ResourceMonitor resourceMonitor;
@Mock
private JobQueue jobQueue;
@Mock private JobQueue jobQueue;
@Captor
private ArgumentCaptor<String> jobIdCaptor;
@Captor private ArgumentCaptor<String> jobIdCaptor;
@BeforeEach
void setUp() {
// Initialize the service manually with all its dependencies
jobExecutorService = new JobExecutorService(
taskManager,
fileStorage,
request,
resourceMonitor,
jobQueue,
30000L, // asyncRequestTimeoutMs
"30m" // sessionTimeout
);
jobExecutorService =
new JobExecutorService(
taskManager,
fileStorage,
request,
resourceMonitor,
jobQueue,
30000L, // asyncRequestTimeoutMs
"30m" // sessionTimeout
);
}
@Test
@@ -109,13 +97,13 @@ class JobExecutorServiceTest {
verify(taskManager).createTask(jobIdCaptor.capture());
}
@Test
void shouldHandleSyncJobError() {
// Given
Supplier<Object> work = () -> {
throw new RuntimeException("Test error");
};
Supplier<Object> work =
() -> {
throw new RuntimeException("Test error");
};
// When
ResponseEntity<?> response = jobExecutorService.runJobGeneric(false, work);
@@ -141,8 +129,7 @@ class JobExecutorServiceTest {
when(jobQueue.queueJob(anyString(), eq(80), any(), anyLong())).thenReturn(future);
// When
ResponseEntity<?> response = jobExecutorService.runJobGeneric(
true, work, 5000, true, 80);
ResponseEntity<?> response = jobExecutorService.runJobGeneric(true, work, 5000, true, 80);
// Then
assertEquals(HttpStatus.OK, response.getStatusCode());
@@ -160,8 +147,9 @@ class JobExecutorServiceTest {
long customTimeout = 60000L;
// Use reflection to access the private executeWithTimeout method
java.lang.reflect.Method executeMethod = JobExecutorService.class
.getDeclaredMethod("executeWithTimeout", Supplier.class, long.class);
java.lang.reflect.Method executeMethod =
JobExecutorService.class.getDeclaredMethod(
"executeWithTimeout", Supplier.class, long.class);
executeMethod.setAccessible(true);
// Create a spy on the JobExecutorService to verify method calls
@@ -177,19 +165,21 @@ class JobExecutorServiceTest {
@Test
void shouldHandleTimeout() throws Exception {
// Given
Supplier<Object> work = () -> {
try {
Thread.sleep(100); // Simulate long-running job
return "test-result";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
};
Supplier<Object> work =
() -> {
try {
Thread.sleep(100); // Simulate long-running job
return "test-result";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
};
// Use reflection to access the private executeWithTimeout method
java.lang.reflect.Method executeMethod = JobExecutorService.class
.getDeclaredMethod("executeWithTimeout", Supplier.class, long.class);
java.lang.reflect.Method executeMethod =
JobExecutorService.class.getDeclaredMethod(
"executeWithTimeout", Supplier.class, long.class);
executeMethod.setAccessible(true);
// When/Then
@@ -1,10 +1,8 @@
package stirling.software.common.service;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Map;
@@ -17,7 +15,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.common.model.job.JobProgress;
import stirling.software.common.service.ResourceMonitor.ResourceStatus;
@ExtendWith(MockitoExtension.class)
@@ -25,16 +22,17 @@ class JobQueueTest {
private JobQueue jobQueue;
@Mock
private ResourceMonitor resourceMonitor;
@Mock private ResourceMonitor resourceMonitor;
private final AtomicReference<ResourceStatus> statusRef = new AtomicReference<>(ResourceStatus.OK);
private final AtomicReference<ResourceStatus> statusRef =
new AtomicReference<>(ResourceStatus.OK);
@BeforeEach
void setUp() {
// Mark stubbing as lenient to avoid UnnecessaryStubbingException
lenient().when(resourceMonitor.calculateDynamicQueueCapacity(anyInt(), anyInt())).thenReturn(10);
lenient()
.when(resourceMonitor.calculateDynamicQueueCapacity(anyInt(), anyInt()))
.thenReturn(10);
lenient().when(resourceMonitor.getCurrentStatus()).thenReturn(statusRef);
// Initialize JobQueue with mocked ResourceMonitor
@@ -50,7 +48,6 @@ class JobQueueTest {
jobQueue.queueJob(jobId, resourceWeight, work, timeoutMs);
assertTrue(jobQueue.isJobQueued(jobId));
assertEquals(1, jobQueue.getTotalQueuedJobs());
}
@@ -1,14 +1,10 @@
package stirling.software.common.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicReference;
@@ -30,20 +26,19 @@ import stirling.software.common.service.ResourceMonitor.ResourceStatus;
@ExtendWith(MockitoExtension.class)
class ResourceMonitorTest {
@InjectMocks
private ResourceMonitor resourceMonitor;
@InjectMocks private ResourceMonitor resourceMonitor;
@Mock
private OperatingSystemMXBean osMXBean;
@Mock private OperatingSystemMXBean osMXBean;
@Mock
private MemoryMXBean memoryMXBean;
@Mock private MemoryMXBean memoryMXBean;
@Spy
private AtomicReference<ResourceStatus> currentStatus = new AtomicReference<>(ResourceStatus.OK);
private AtomicReference<ResourceStatus> currentStatus =
new AtomicReference<>(ResourceStatus.OK);
@Spy
private AtomicReference<ResourceMetrics> latestMetrics = new AtomicReference<>(new ResourceMetrics());
private AtomicReference<ResourceMetrics> latestMetrics =
new AtomicReference<>(new ResourceMetrics());
@BeforeEach
void setUp() {
@@ -92,23 +87,26 @@ class ResourceMonitorTest {
assertEquals(3, capacity, "With CRITICAL status, capacity should be reduced to 30%");
// Test minimum capacity enforcement
assertEquals(minCapacity, resourceMonitor.calculateDynamicQueueCapacity(1, minCapacity),
assertEquals(
minCapacity,
resourceMonitor.calculateDynamicQueueCapacity(1, minCapacity),
"Should never go below minimum capacity");
}
@ParameterizedTest
@CsvSource({
"10, OK, false", // Light job, OK status
"10, OK, false", // Light job, OK status
"10, WARNING, false", // Light job, WARNING status
"10, CRITICAL, true", // Light job, CRITICAL status
"30, OK, false", // Medium job, OK status
"30, WARNING, true", // Medium job, WARNING status
"30, OK, false", // Medium job, OK status
"30, WARNING, true", // Medium job, WARNING status
"30, CRITICAL, true", // Medium job, CRITICAL status
"80, OK, true", // Heavy job, OK status
"80, WARNING, true", // Heavy job, WARNING status
"80, CRITICAL, true" // Heavy job, CRITICAL status
"80, OK, true", // Heavy job, OK status
"80, WARNING, true", // Heavy job, WARNING status
"80, CRITICAL, true" // Heavy job, CRITICAL status
})
void shouldQueueJobBasedOnWeightAndStatus(int weight, ResourceStatus status, boolean shouldQueue) {
void shouldQueueJobBasedOnWeightAndStatus(
int weight, ResourceStatus status, boolean shouldQueue) {
// Given
currentStatus.set(status);
@@ -116,8 +114,11 @@ class ResourceMonitorTest {
boolean result = resourceMonitor.shouldQueueJob(weight);
// Then
assertEquals(shouldQueue, result,
String.format("For weight %d and status %s, shouldQueue should be %s",
assertEquals(
shouldQueue,
result,
String.format(
"For weight %d and status %s, shouldQueue should be %s",
weight, status, shouldQueue));
}
@@ -131,7 +132,9 @@ class ResourceMonitorTest {
ResourceMetrics freshMetrics = new ResourceMetrics(0.5, 0.5, 1024, 2048, 4096, now);
// When/Then
assertTrue(staleMetrics.isStale(5000), "Metrics from 6 seconds ago should be stale with 5s threshold");
assertTrue(
staleMetrics.isStale(5000),
"Metrics from 6 seconds ago should be stale with 5s threshold");
assertFalse(freshMetrics.isStale(5000), "Fresh metrics should not be stale");
}
}
@@ -6,7 +6,6 @@ import static org.mockito.Mockito.*;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -22,11 +21,9 @@ import stirling.software.common.model.job.ResultFile;
class TaskManagerTest {
@Mock
private FileStorage fileStorage;
@Mock private FileStorage fileStorage;
@InjectMocks
private TaskManager taskManager;
@InjectMocks private TaskManager taskManager;
private AutoCloseable closeable;
@@ -234,18 +231,20 @@ class TaskManagerTest {
ReflectionTestUtils.setField(oldJob, "complete", true);
// Create a ResultFile and set it using the new approach
ResultFile resultFile = ResultFile.builder()
.fileId("file-id")
.fileName("test.pdf")
.contentType("application/pdf")
.fileSize(1024L)
.build();
ResultFile resultFile =
ResultFile.builder()
.fileId("file-id")
.fileName("test.pdf")
.contentType("application/pdf")
.fileSize(1024L)
.build();
ReflectionTestUtils.setField(oldJob, "resultFiles", java.util.List.of(resultFile));
when(fileStorage.deleteFile("file-id")).thenReturn(true);
// Obtain access to the private jobResults map
Map<String, JobResult> jobResultsMap = (Map<String, JobResult>) ReflectionTestUtils.getField(taskManager, "jobResults");
Map<String, JobResult> jobResultsMap =
(Map<String, JobResult>) ReflectionTestUtils.getField(taskManager, "jobResults");
// 3. Create an active job
String activeJobId = "active-job";
@@ -12,7 +12,6 @@ import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Stream;
@@ -30,31 +29,22 @@ import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.TempFileRegistry;
/**
* Tests for the TempFileCleanupService, focusing on its pattern-matching and cleanup logic.
*/
/** Tests for the TempFileCleanupService, focusing on its pattern-matching and cleanup logic. */
public class TempFileCleanupServiceTest {
@TempDir
Path tempDir;
@TempDir Path tempDir;
@Mock
private TempFileRegistry registry;
@Mock private TempFileRegistry registry;
@Mock
private TempFileManager tempFileManager;
@Mock private TempFileManager tempFileManager;
@Mock
private ApplicationProperties applicationProperties;
@Mock private ApplicationProperties applicationProperties;
@Mock
private ApplicationProperties.System system;
@Mock private ApplicationProperties.System system;
@Mock
private ApplicationProperties.TempFileManagement tempFileManagement;
@Mock private ApplicationProperties.TempFileManagement tempFileManagement;
@InjectMocks
private TempFileCleanupService cleanupService;
@InjectMocks private TempFileCleanupService cleanupService;
private Path systemTempDir;
private Path customTempDir;
@@ -124,7 +114,8 @@ public class TempFileCleanupServiceTest {
// Files that should be preserved
Path jettyFile1 = Files.createFile(systemTempDir.resolve("jetty-123.tmp"));
Path jettyFile2 = Files.createFile(systemTempDir.resolve("something-with-jetty-inside.tmp"));
Path jettyFile2 =
Files.createFile(systemTempDir.resolve("something-with-jetty-inside.tmp"));
Path regularFile = Files.createFile(systemTempDir.resolve("important.txt"));
// Create a nested directory with temp files
@@ -143,19 +134,29 @@ public class TempFileCleanupServiceTest {
// Use MockedStatic to mock Files operations
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
// Mock Files.list for each directory we'll process
mockedFiles.when(() -> Files.list(eq(systemTempDir)))
.thenReturn(Stream.of(
ourTempFile1, ourTempFile2, oldTempFile, sysTempFile1,
jettyFile1, jettyFile2, regularFile, emptyFile, nestedDir));
mockedFiles
.when(() -> Files.list(eq(systemTempDir)))
.thenReturn(
Stream.of(
ourTempFile1,
ourTempFile2,
oldTempFile,
sysTempFile1,
jettyFile1,
jettyFile2,
regularFile,
emptyFile,
nestedDir));
mockedFiles.when(() -> Files.list(eq(customTempDir)))
mockedFiles
.when(() -> Files.list(eq(customTempDir)))
.thenReturn(Stream.of(ourTempFile3, ourTempFile4, sysTempFile2, sysTempFile3));
mockedFiles.when(() -> Files.list(eq(libreOfficeTempDir)))
mockedFiles
.when(() -> Files.list(eq(libreOfficeTempDir)))
.thenReturn(Stream.of(ourTempFile5));
mockedFiles.when(() -> Files.list(eq(nestedDir)))
.thenReturn(Stream.of(nestedTempFile));
mockedFiles.when(() -> Files.list(eq(nestedDir))).thenReturn(Stream.of(nestedTempFile));
// Configure Files.isDirectory for each path
mockedFiles.when(() -> Files.isDirectory(eq(nestedDir))).thenReturn(true);
@@ -165,48 +166,59 @@ public class TempFileCleanupServiceTest {
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
// Configure Files.getLastModifiedTime to return different times based on file names
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenAnswer(invocation -> {
Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString();
mockedFiles
.when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString();
// For files with "old" in the name, return a timestamp older than maxAgeMillis
if (fileName.contains("old")) {
return FileTime.fromMillis(System.currentTimeMillis() - 5000000);
}
// For empty.tmp file, return a timestamp older than 5 minutes (for empty file test)
else if (fileName.equals("empty.tmp")) {
return FileTime.fromMillis(System.currentTimeMillis() - 6 * 60 * 1000);
}
// For all other files, return a recent timestamp
else {
return FileTime.fromMillis(System.currentTimeMillis() - 60000); // 1 minute ago
}
});
// For files with "old" in the name, return a timestamp older than
// maxAgeMillis
if (fileName.contains("old")) {
return FileTime.fromMillis(
System.currentTimeMillis() - 5000000);
}
// For empty.tmp file, return a timestamp older than 5 minutes (for
// empty file test)
else if (fileName.equals("empty.tmp")) {
return FileTime.fromMillis(
System.currentTimeMillis() - 6 * 60 * 1000);
}
// For all other files, return a recent timestamp
else {
return FileTime.fromMillis(
System.currentTimeMillis() - 60000); // 1 minute ago
}
});
// Configure Files.size to return different sizes based on file names
mockedFiles.when(() -> Files.size(any(Path.class)))
.thenAnswer(invocation -> {
Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString();
mockedFiles
.when(() -> Files.size(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString();
// Return 0 bytes for the empty file
if (fileName.equals("empty.tmp")) {
return 0L;
}
// Return normal size for all other files
else {
return 1024L; // 1 KB
}
});
// Return 0 bytes for the empty file
if (fileName.equals("empty.tmp")) {
return 0L;
}
// Return normal size for all other files
else {
return 1024L; // 1 KB
}
});
// For deleteIfExists, track which files would be deleted
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(invocation -> {
Path path = invocation.getArgument(0);
deletedFiles.add(path);
return true;
});
mockedFiles
.when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0);
deletedFiles.add(path);
return true;
});
// Act - set containerMode to false for this test
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
@@ -218,20 +230,33 @@ public class TempFileCleanupServiceTest {
assertTrue(deletedFiles.contains(emptyFile), "Empty file should be deleted");
// Regular temp files should not be deleted because they're too new
assertFalse(deletedFiles.contains(ourTempFile1), "Recent temp file should be preserved");
assertFalse(deletedFiles.contains(ourTempFile2), "Recent temp file should be preserved");
assertFalse(deletedFiles.contains(ourTempFile3), "Recent temp file should be preserved");
assertFalse(deletedFiles.contains(ourTempFile4), "Recent temp file should be preserved");
assertFalse(deletedFiles.contains(ourTempFile5), "Recent temp file should be preserved");
assertFalse(
deletedFiles.contains(ourTempFile1), "Recent temp file should be preserved");
assertFalse(
deletedFiles.contains(ourTempFile2), "Recent temp file should be preserved");
assertFalse(
deletedFiles.contains(ourTempFile3), "Recent temp file should be preserved");
assertFalse(
deletedFiles.contains(ourTempFile4), "Recent temp file should be preserved");
assertFalse(
deletedFiles.contains(ourTempFile5), "Recent temp file should be preserved");
// System temp files should not be deleted in non-container mode
assertFalse(deletedFiles.contains(sysTempFile1), "System temp file should be preserved in non-container mode");
assertFalse(deletedFiles.contains(sysTempFile2), "System temp file should be preserved in non-container mode");
assertFalse(deletedFiles.contains(sysTempFile3), "System temp file should be preserved in non-container mode");
assertFalse(
deletedFiles.contains(sysTempFile1),
"System temp file should be preserved in non-container mode");
assertFalse(
deletedFiles.contains(sysTempFile2),
"System temp file should be preserved in non-container mode");
assertFalse(
deletedFiles.contains(sysTempFile3),
"System temp file should be preserved in non-container mode");
// Jetty files and regular files should never be deleted
assertFalse(deletedFiles.contains(jettyFile1), "Jetty file should be preserved");
assertFalse(deletedFiles.contains(jettyFile2), "File with jetty in name should be preserved");
assertFalse(
deletedFiles.contains(jettyFile2),
"File with jetty in name should be preserved");
assertFalse(deletedFiles.contains(regularFile), "Regular file should be preserved");
}
}
@@ -252,7 +277,8 @@ public class TempFileCleanupServiceTest {
// Use MockedStatic to mock Files operations
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
// Mock Files.list for systemTempDir
mockedFiles.when(() -> Files.list(eq(systemTempDir)))
mockedFiles
.when(() -> Files.list(eq(systemTempDir)))
.thenReturn(Stream.of(ourTempFile, sysTempFile, regularFile));
// Configure Files.isDirectory
@@ -262,28 +288,37 @@ public class TempFileCleanupServiceTest {
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
// Configure Files.getLastModifiedTime to return recent timestamps
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenReturn(FileTime.fromMillis(System.currentTimeMillis() - 60000)); // 1 minute ago
mockedFiles
.when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenReturn(
FileTime.fromMillis(
System.currentTimeMillis() - 60000)); // 1 minute ago
// Configure Files.size to return normal size
mockedFiles.when(() -> Files.size(any(Path.class)))
.thenReturn(1024L); // 1 KB
mockedFiles.when(() -> Files.size(any(Path.class))).thenReturn(1024L); // 1 KB
// For deleteIfExists, track which files would be deleted
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(invocation -> {
Path path = invocation.getArgument(0);
deletedFiles.add(path);
return true;
});
mockedFiles
.when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0);
deletedFiles.add(path);
return true;
});
// Act - set containerMode to true and maxAgeMillis to 0 for container startup cleanup
invokeCleanupDirectoryStreaming(systemTempDir, true, 0, 0);
// Assert - In container mode, both our temp files and system temp files should be deleted
// Assert - In container mode, both our temp files and system temp files should be
// deleted
// regardless of age (when maxAgeMillis is 0)
assertTrue(deletedFiles.contains(ourTempFile), "Our temp file should be deleted in container mode");
assertTrue(deletedFiles.contains(sysTempFile), "System temp file should be deleted in container mode");
assertTrue(
deletedFiles.contains(ourTempFile),
"Our temp file should be deleted in container mode");
assertTrue(
deletedFiles.contains(sysTempFile),
"System temp file should be deleted in container mode");
assertFalse(deletedFiles.contains(regularFile), "Regular file should be preserved");
}
}
@@ -303,7 +338,8 @@ public class TempFileCleanupServiceTest {
// Use MockedStatic to mock Files operations
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
// Mock Files.list for systemTempDir
mockedFiles.when(() -> Files.list(eq(systemTempDir)))
mockedFiles
.when(() -> Files.list(eq(systemTempDir)))
.thenReturn(Stream.of(emptyFile, recentEmptyFile));
// Configure Files.isDirectory
@@ -313,39 +349,46 @@ public class TempFileCleanupServiceTest {
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
// Configure Files.getLastModifiedTime to return different times based on file names
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenAnswer(invocation -> {
Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString();
mockedFiles
.when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString();
if (fileName.equals("empty.tmp")) {
// More than 5 minutes old
return FileTime.fromMillis(System.currentTimeMillis() - 6 * 60 * 1000);
} else {
// Less than 5 minutes old
return FileTime.fromMillis(System.currentTimeMillis() - 2 * 60 * 1000);
}
});
if (fileName.equals("empty.tmp")) {
// More than 5 minutes old
return FileTime.fromMillis(
System.currentTimeMillis() - 6 * 60 * 1000);
} else {
// Less than 5 minutes old
return FileTime.fromMillis(
System.currentTimeMillis() - 2 * 60 * 1000);
}
});
// Configure Files.size to return 0 for empty files
mockedFiles.when(() -> Files.size(any(Path.class)))
.thenReturn(0L);
mockedFiles.when(() -> Files.size(any(Path.class))).thenReturn(0L);
// For deleteIfExists, track which files would be deleted
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(invocation -> {
Path path = invocation.getArgument(0);
deletedFiles.add(path);
return true;
});
mockedFiles
.when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0);
deletedFiles.add(path);
return true;
});
// Act
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
// Assert
assertTrue(deletedFiles.contains(emptyFile),
assertTrue(
deletedFiles.contains(emptyFile),
"Empty file older than 5 minutes should be deleted");
assertFalse(deletedFiles.contains(recentEmptyFile),
assertFalse(
deletedFiles.contains(recentEmptyFile),
"Empty file newer than 5 minutes should not be deleted");
}
}
@@ -370,17 +413,13 @@ public class TempFileCleanupServiceTest {
// Use MockedStatic to mock Files operations
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
// Mock Files.list for each directory
mockedFiles.when(() -> Files.list(eq(systemTempDir)))
.thenReturn(Stream.of(dir1));
mockedFiles.when(() -> Files.list(eq(systemTempDir))).thenReturn(Stream.of(dir1));
mockedFiles.when(() -> Files.list(eq(dir1)))
.thenReturn(Stream.of(tempFile1, dir2));
mockedFiles.when(() -> Files.list(eq(dir1))).thenReturn(Stream.of(tempFile1, dir2));
mockedFiles.when(() -> Files.list(eq(dir2)))
.thenReturn(Stream.of(tempFile2, dir3));
mockedFiles.when(() -> Files.list(eq(dir2))).thenReturn(Stream.of(tempFile2, dir3));
mockedFiles.when(() -> Files.list(eq(dir3)))
.thenReturn(Stream.of(tempFile3));
mockedFiles.when(() -> Files.list(eq(dir3))).thenReturn(Stream.of(tempFile3));
// Configure Files.isDirectory for each path
mockedFiles.when(() -> Files.isDirectory(eq(dir1))).thenReturn(true);
@@ -394,31 +433,35 @@ public class TempFileCleanupServiceTest {
mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(true);
// Configure Files.getLastModifiedTime to return different times based on file names
mockedFiles.when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenAnswer(invocation -> {
Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString();
mockedFiles
.when(() -> Files.getLastModifiedTime(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0);
String fileName = path.getFileName().toString();
if (fileName.contains("old")) {
// Old file
return FileTime.fromMillis(System.currentTimeMillis() - 5000000);
} else {
// Recent file
return FileTime.fromMillis(System.currentTimeMillis() - 60000);
}
});
if (fileName.contains("old")) {
// Old file
return FileTime.fromMillis(
System.currentTimeMillis() - 5000000);
} else {
// Recent file
return FileTime.fromMillis(System.currentTimeMillis() - 60000);
}
});
// Configure Files.size to return normal size
mockedFiles.when(() -> Files.size(any(Path.class)))
.thenReturn(1024L);
mockedFiles.when(() -> Files.size(any(Path.class))).thenReturn(1024L);
// For deleteIfExists, track which files would be deleted
mockedFiles.when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(invocation -> {
Path path = invocation.getArgument(0);
deletedFiles.add(path);
return true;
});
mockedFiles
.when(() -> Files.deleteIfExists(any(Path.class)))
.thenAnswer(
invocation -> {
Path path = invocation.getArgument(0);
deletedFiles.add(path);
return true;
});
// Act
invokeCleanupDirectoryStreaming(systemTempDir, false, 0, 3600000);
@@ -430,14 +473,15 @@ public class TempFileCleanupServiceTest {
// Assert
assertFalse(deletedFiles.contains(tempFile1), "Recent temp file should be preserved");
assertFalse(deletedFiles.contains(tempFile2), "Recent temp file should be preserved");
assertTrue(deletedFiles.contains(tempFile3), "Old temp file in nested directory should be deleted");
assertTrue(
deletedFiles.contains(tempFile3),
"Old temp file in nested directory should be deleted");
}
}
/**
* Helper method to invoke the private cleanupDirectoryStreaming method using reflection
*/
private void invokeCleanupDirectoryStreaming(Path directory, boolean containerMode, int depth, long maxAgeMillis)
/** Helper method to invoke the private cleanupDirectoryStreaming method using reflection */
private void invokeCleanupDirectoryStreaming(
Path directory, boolean containerMode, int depth, long maxAgeMillis)
throws IOException {
try {
// Create a consumer that tracks deleted files
@@ -445,13 +489,26 @@ public class TempFileCleanupServiceTest {
Consumer<Path> deleteCallback = path -> deleteCount.incrementAndGet();
// Get the method with updated signature
var method = TempFileCleanupService.class.getDeclaredMethod(
"cleanupDirectoryStreaming",
Path.class, boolean.class, int.class, long.class, boolean.class, Consumer.class);
var method =
TempFileCleanupService.class.getDeclaredMethod(
"cleanupDirectoryStreaming",
Path.class,
boolean.class,
int.class,
long.class,
boolean.class,
Consumer.class);
method.setAccessible(true);
// Invoke the method with appropriate parameters
method.invoke(cleanupService, directory, containerMode, depth, maxAgeMillis, false, deleteCallback);
method.invoke(
cleanupService,
directory,
containerMode,
depth,
maxAgeMillis,
false,
deleteCallback);
} catch (Exception e) {
throw new RuntimeException("Error invoking cleanupDirectoryStreaming", e);
}
@@ -1,14 +1,5 @@
package stirling.software.common.util;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -19,6 +10,18 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
class CheckProgramInstallTest {
private MockedStatic<ProcessExecutor> mockProcessExecutor;
@@ -3,21 +3,46 @@ package stirling.software.common.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import stirling.software.common.service.SsrfProtectionService;
class CustomHtmlSanitizerTest {
private CustomHtmlSanitizer customHtmlSanitizer;
@BeforeEach
void setUp() {
SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class);
stirling.software.common.model.ApplicationProperties mockApplicationProperties =
mock(stirling.software.common.model.ApplicationProperties.class);
stirling.software.common.model.ApplicationProperties.System mockSystem =
mock(stirling.software.common.model.ApplicationProperties.System.class);
// Allow all URLs by default for basic tests
when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString()))
.thenReturn(true);
when(mockApplicationProperties.getSystem()).thenReturn(mockSystem);
when(mockSystem.getDisableSanitize()).thenReturn(false); // Enable sanitization for tests
customHtmlSanitizer =
new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties);
}
@ParameterizedTest
@MethodSource("provideHtmlTestCases")
void testSanitizeHtml(String inputHtml, String[] expectedContainedTags) {
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(inputHtml);
String sanitizedHtml = customHtmlSanitizer.sanitize(inputHtml);
// Assert
for (String tag : expectedContainedTags) {
@@ -58,7 +83,7 @@ class CustomHtmlSanitizerTest {
"<p style=\"color: blue; font-size: 16px; margin-top: 10px;\">Styled text</p>";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithStyles);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithStyles);
// Assert
// The OWASP HTML Sanitizer might filter some specific styles, so we only check that
@@ -75,7 +100,7 @@ class CustomHtmlSanitizerTest {
"<a href=\"https://example.com\" title=\"Example Site\">Example Link</a>";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithLink);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithLink);
// Assert
// The most important aspect is that the link content is preserved
@@ -97,7 +122,7 @@ class CustomHtmlSanitizerTest {
String htmlWithJsLink = "<a href=\"javascript:alert('XSS')\">Malicious Link</a>";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithJsLink);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithJsLink);
// Assert
assertFalse(sanitizedHtml.contains("javascript:"), "JavaScript URLs should be removed");
@@ -116,7 +141,7 @@ class CustomHtmlSanitizerTest {
+ "</table>";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithTable);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithTable);
// Assert
assertTrue(sanitizedHtml.contains("<table"), "Table should be preserved");
@@ -143,7 +168,7 @@ class CustomHtmlSanitizerTest {
"<img src=\"image.jpg\" alt=\"An image\" width=\"100\" height=\"100\">";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithImage);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithImage);
// Assert
assertTrue(sanitizedHtml.contains("<img"), "Image tag should be preserved");
@@ -160,7 +185,7 @@ class CustomHtmlSanitizerTest {
"<img src=\"data:image/svg+xml;base64,PHN2ZyBvbmxvYWQ9ImFsZXJ0KDEpIj48L3N2Zz4=\" alt=\"SVG with XSS\">";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithDataUrlImage);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithDataUrlImage);
// Assert
assertFalse(
@@ -175,7 +200,7 @@ class CustomHtmlSanitizerTest {
"<a href=\"#\" onclick=\"alert('XSS')\" onmouseover=\"alert('XSS')\">Click me</a>";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithJsEvent);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithJsEvent);
// Assert
assertFalse(
@@ -192,7 +217,7 @@ class CustomHtmlSanitizerTest {
String htmlWithScript = "<p>Safe content</p><script>alert('XSS');</script>";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithScript);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithScript);
// Assert
assertFalse(sanitizedHtml.contains("<script>"), "Script tags should be removed");
@@ -206,7 +231,7 @@ class CustomHtmlSanitizerTest {
String htmlWithNoscript = "<p>Safe content</p><noscript>JavaScript is disabled</noscript>";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithNoscript);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithNoscript);
// Assert
assertFalse(sanitizedHtml.contains("<noscript>"), "Noscript tags should be removed");
@@ -220,7 +245,7 @@ class CustomHtmlSanitizerTest {
String htmlWithIframe = "<p>Safe content</p><iframe src=\"https://example.com\"></iframe>";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithIframe);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithIframe);
// Assert
assertFalse(sanitizedHtml.contains("<iframe"), "Iframe tags should be removed");
@@ -237,7 +262,7 @@ class CustomHtmlSanitizerTest {
+ "<embed src=\"embed.swf\" type=\"application/x-shockwave-flash\">";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithObjects);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithObjects);
// Assert
assertFalse(sanitizedHtml.contains("<object"), "Object tags should be removed");
@@ -256,7 +281,7 @@ class CustomHtmlSanitizerTest {
+ "<link rel=\"stylesheet\" href=\"evil.css\">";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(htmlWithMetaTags);
String sanitizedHtml = customHtmlSanitizer.sanitize(htmlWithMetaTags);
// Assert
assertFalse(sanitizedHtml.contains("<meta"), "Meta tags should be removed");
@@ -283,7 +308,7 @@ class CustomHtmlSanitizerTest {
+ "</div>";
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(complexHtml);
String sanitizedHtml = customHtmlSanitizer.sanitize(complexHtml);
// Assert
assertTrue(sanitizedHtml.contains("<div"), "Div should be preserved");
@@ -314,7 +339,7 @@ class CustomHtmlSanitizerTest {
@Test
void testSanitizeHandlesEmpty() {
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize("");
String sanitizedHtml = customHtmlSanitizer.sanitize("");
// Assert
assertEquals("", sanitizedHtml, "Empty input should result in empty string");
@@ -323,7 +348,7 @@ class CustomHtmlSanitizerTest {
@Test
void testSanitizeHandlesNull() {
// Act
String sanitizedHtml = CustomHtmlSanitizer.sanitize(null);
String sanitizedHtml = customHtmlSanitizer.sanitize(null);
// Assert
assertEquals("", sanitizedHtml, "Null input should result in empty string");
File diff suppressed because it is too large Load Diff
@@ -19,6 +19,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.common.configuration.RuntimePathConfig;
@ExtendWith(MockitoExtension.class)
@@ -3,19 +3,40 @@ package stirling.software.common.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.ArgumentMatchers.anyString;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
import stirling.software.common.service.SsrfProtectionService;
public class FileToPdfTest {
private CustomHtmlSanitizer customHtmlSanitizer;
@BeforeEach
void setUp() {
SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class);
stirling.software.common.model.ApplicationProperties mockApplicationProperties =
mock(stirling.software.common.model.ApplicationProperties.class);
stirling.software.common.model.ApplicationProperties.System mockSystem =
mock(stirling.software.common.model.ApplicationProperties.System.class);
when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString()))
.thenReturn(true);
when(mockApplicationProperties.getSystem()).thenReturn(mockSystem);
when(mockSystem.getDisableSanitize()).thenReturn(false);
customHtmlSanitizer =
new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties);
}
/**
* Test the HTML to PDF conversion. This test expects an IOException when an empty HTML input is
* provided.
@@ -25,14 +46,13 @@ public class FileToPdfTest {
HTMLToPdfRequest request = new HTMLToPdfRequest();
byte[] fileBytes = new byte[0]; // Sample file bytes (empty input)
String fileName = "test.html"; // Sample file name indicating an HTML file
boolean disableSanitize = false; // Flag to control sanitization
TempFileManager tempFileManager = mock(TempFileManager.class); // Mock TempFileManager
// Mock the temp file creation to return real temp files
try {
when(tempFileManager.createTempFile(anyString()))
.thenReturn(File.createTempFile("test", ".pdf"))
.thenReturn(File.createTempFile("test", ".html"));
.thenReturn(Files.createTempFile("test", ".pdf").toFile())
.thenReturn(Files.createTempFile("test", ".html").toFile());
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -43,7 +63,12 @@ public class FileToPdfTest {
Exception.class,
() ->
FileToPdf.convertHtmlToPdf(
"/path/", request, fileBytes, fileName, disableSanitize, tempFileManager));
"/path/",
request,
fileBytes,
fileName,
tempFileManager,
customHtmlSanitizer));
assertNotNull(thrown);
}
@@ -1,21 +1,23 @@
package stirling.software.common.util;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.junit.jupiter.MockitoExtension;
import stirling.software.common.model.enumeration.UsernameAttribute;
import stirling.software.common.model.oauth2.GitHubProvider;
import stirling.software.common.model.oauth2.GoogleProvider;
import stirling.software.common.model.oauth2.Provider;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ProviderUtilsTest {
@@ -40,7 +42,7 @@ class ProviderUtilsTest {
public static Stream<Arguments> providerParams() {
Provider generic = null;
var google =
new GoogleProvider(null, "clientSecret", List.of("scope"), UsernameAttribute.EMAIL);
new GoogleProvider(null, "clientSecret", List.of("scope"), UsernameAttribute.EMAIL);
var github = new GitHubProvider("clientId", "", List.of("scope"), UsernameAttribute.LOGIN);
return Stream.of(Arguments.of(generic), Arguments.of(google), Arguments.of(github));
@@ -42,7 +42,6 @@ class SpringContextHolderTest {
verify(mockApplicationContext).getBean(TestBean.class);
}
@Test
void testGetBean_ApplicationContextNotSet() {
// Don't set application context
@@ -58,7 +57,8 @@ class SpringContextHolderTest {
void testGetBean_BeanNotFound() {
// Arrange
contextHolder.setApplicationContext(mockApplicationContext);
when(mockApplicationContext.getBean(TestBean.class)).thenThrow(new org.springframework.beans.BeansException("Bean not found") {});
when(mockApplicationContext.getBean(TestBean.class))
.thenThrow(new org.springframework.beans.BeansException("Bean not found") {});
// Act
TestBean result = SpringContextHolder.getBean(TestBean.class);
@@ -68,6 +68,5 @@ class SpringContextHolderTest {
}
// Simple test class
private static class TestBean {
}
private static class TestBean {}
}
@@ -1,11 +1,13 @@
package stirling.software.common.util.misc;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.api.misc.HighContrastColorCombination;
import stirling.software.common.model.api.misc.ReplaceAndInvert;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.api.misc.HighContrastColorCombination;
import stirling.software.common.model.api.misc.ReplaceAndInvert;
class HighContrastColorReplaceDeciderTest {
@Test
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.io.InputStreamResource;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.api.misc.ReplaceAndInvert;
class InvertFullColorStrategyTest {
@@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.io.InputStreamResource;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.common.model.api.misc.ReplaceAndInvert;
class ReplaceAndInvertColorStrategyTest {
@@ -1,14 +1,17 @@
package stirling.software.common.util.propertyeditor;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.api.security.RedactionArea;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import stirling.software.common.model.api.security.RedactionArea;
class StringToArrayListPropertyEditorTest {
private StringToArrayListPropertyEditor editor;
@@ -0,0 +1 @@
ok
+3 -2
View File
@@ -16,8 +16,7 @@ local.properties
version.properties
#### Stirling-PDF Files ###
pipeline/watchedFolders/
pipeline/finishedFolders/
pipeline/*
customFiles/
configs/
watchedFolders/
@@ -194,3 +193,5 @@ id_ed25519.pub
# node_modules
node_modules/
scripts/**/*
+17 -5
View File
@@ -14,7 +14,7 @@ configurations {
spotless {
java {
target sourceSets.main.allJava
target 'src/**/java/**/*.java'
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
importOrder("java", "javax", "org", "com", "net", "io", "jakarta", "lombok", "me", "stirling")
@@ -23,6 +23,18 @@ spotless {
leadingTabsToSpaces()
endWithNewline()
}
yaml {
target '**/*.yml', '**/*.yaml'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
format 'gradle', {
target '**/gradle/*.gradle', '**/*.gradle'
trimTrailingWhitespace()
leadingTabsToSpaces()
endWithNewline()
}
}
dependencies {
@@ -43,10 +55,10 @@ dependencies {
implementation project(':common')
implementation 'org.springframework.boot:spring-boot-starter-jetty'
implementation 'com.posthog.java:posthog:1.2.0'
implementation 'commons-io:commons-io:2.19.0'
implementation 'commons-io:commons-io:2.20.0'
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation 'io.micrometer:micrometer-core:1.15.1'
implementation 'io.micrometer:micrometer-core:1.15.2'
implementation 'com.google.zxing:core:3.5.3'
implementation "org.commonmark:commonmark:$commonmarkVersion" // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark-ext-gfm-tables:$commonmarkVersion"
@@ -62,7 +74,7 @@ dependencies {
exclude group: 'com.google.code.gson', module: 'gson'
}
implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4'
implementation 'com.opencsv:opencsv:5.11.2' // https://mvnrepository.com/artifact/com.opencsv/opencsv
implementation 'com.opencsv:opencsv:5.12.0' // https://mvnrepository.com/artifact/com.opencsv/opencsv
// Batik
implementation 'org.apache.xmlgraphics:batik-all:1.19'
@@ -79,7 +91,7 @@ dependencies {
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pcx:$imageioVersion@
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pict:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pnm:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-psd:$imageioVersion"
runtimeOnly "com.twelvemonkeys.imageio:imageio-psd:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-sgi:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-tga:$imageioVersion"
// runtimeOnly "com.twelvemonkeys.imageio:imageio-thumbsdb:$imageioVersion"
@@ -10,7 +10,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
@@ -23,7 +22,6 @@ import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.common.configuration.AppConfig;
import stirling.software.common.configuration.ConfigInitializer;
import stirling.software.common.configuration.InstallationPathConfig;
@@ -47,17 +45,15 @@ public class SPDFApplication {
private final AppConfig appConfig;
private final Environment env;
private final ApplicationProperties applicationProperties;
private final WebBrowser webBrowser;
// private final WebBrowser webBrowser; // Removed - desktop UI eliminated
public SPDFApplication(
AppConfig appConfig,
Environment env,
ApplicationProperties applicationProperties,
@Autowired(required = false) WebBrowser webBrowser) {
AppConfig appConfig, Environment env, ApplicationProperties applicationProperties) {
this.appConfig = appConfig;
this.env = env;
this.applicationProperties = applicationProperties;
this.webBrowser = webBrowser;
// this.webBrowser = webBrowser; // Removed - desktop UI eliminated
}
public static void main(String[] args) throws IOException, InterruptedException {
@@ -151,28 +147,31 @@ public class SPDFApplication {
serverPortStatic = serverPort;
String url = baseUrl + ":" + getStaticPort() + contextPath;
if (webBrowser != null
&& Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
webBrowser.initWebUI(url);
} else {
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
if (browserOpen) {
try {
String os = System.getProperty("os.name").toLowerCase();
Runtime rt = Runtime.getRuntime();
// Desktop UI initialization removed - webBrowser dependency eliminated
// Keep backwards compatibility for STIRLING_PDF_DESKTOP_UI system property
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
log.info("Desktop UI mode enabled, but WebBrowser functionality has been removed");
// webBrowser.initWebUI(url); // Removed - desktop UI eliminated
}
if (os.contains("win")) {
// For Windows
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
} else if (os.contains("mac")) {
SystemCommand.runCommand(rt, "open " + url);
} else if (os.contains("nix") || os.contains("nux")) {
SystemCommand.runCommand(rt, "xdg-open " + url);
}
} catch (IOException e) {
log.error("Error opening browser: {}", e.getMessage());
// Standard browser opening logic
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
if (browserOpen) {
try {
String os = System.getProperty("os.name").toLowerCase();
Runtime rt = Runtime.getRuntime();
if (os.contains("win")) {
// For Windows
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
} else if (os.contains("mac")) {
SystemCommand.runCommand(rt, "open " + url);
} else if (os.contains("nix") || os.contains("nux")) {
SystemCommand.runCommand(rt, "xdg-open " + url);
}
} catch (IOException e) {
log.error("Error opening browser: {}", e.getMessage());
}
}
}
@@ -189,9 +188,10 @@ public class SPDFApplication {
@PreDestroy
public void cleanup() {
if (webBrowser != null) {
webBrowser.cleanup();
}
// webBrowser cleanup removed - desktop UI eliminated
// if (webBrowser != null) {
// webBrowser.cleanup();
// }
}
private static void printStartupLogs() {
@@ -1,7 +0,0 @@
package stirling.software.SPDF.UI;
public interface WebBrowser {
void initWebUI(String url);
void cleanup();
}
@@ -1,497 +0,0 @@
package stirling.software.SPDF.UI.impl;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.io.File;
import java.io.InputStream;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.callback.CefBeforeDownloadCallback;
import org.cef.callback.CefDownloadItem;
import org.cef.callback.CefDownloadItemCallback;
import org.cef.handler.CefDownloadHandlerAdapter;
import org.cef.handler.CefLoadHandlerAdapter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import me.friwi.jcefmaven.CefAppBuilder;
import me.friwi.jcefmaven.EnumProgress;
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.util.UIScaling;
@Component
@Slf4j
@ConditionalOnProperty(
name = "STIRLING_PDF_DESKTOP_UI",
havingValue = "true",
matchIfMissing = false)
public class DesktopBrowser implements WebBrowser {
private static CefApp cefApp;
private static CefClient client;
private static CefBrowser browser;
private static JFrame frame;
private static LoadingWindow loadingWindow;
private static volatile boolean browserInitialized = false;
private static TrayIcon trayIcon;
private static SystemTray systemTray;
public DesktopBrowser() {
SwingUtilities.invokeLater(
() -> {
loadingWindow = new LoadingWindow(null, "Initializing...");
loadingWindow.setVisible(true);
});
}
public void initWebUI(String url) {
CompletableFuture.runAsync(
() -> {
try {
CefAppBuilder builder = new CefAppBuilder();
configureCefSettings(builder);
builder.setProgressHandler(createProgressHandler());
builder.setInstallDir(
new File(InstallationPathConfig.getClientWebUIPath()));
// Build and initialize CEF
cefApp = builder.build();
client = cefApp.createClient();
// Set up download handler
setupDownloadHandler();
// Create browser and frame on EDT
SwingUtilities.invokeAndWait(
() -> {
browser = client.createBrowser(url, false, false);
setupMainFrame();
setupLoadHandler();
// Force initialize UI after 7 seconds if not already done
Timer timeoutTimer =
new Timer(
2500,
e -> {
log.warn(
"Loading timeout reached. Forcing"
+ " UI transition.");
if (!browserInitialized) {
// Force UI initialization
forceInitializeUI();
}
});
timeoutTimer.setRepeats(false);
timeoutTimer.start();
});
} catch (Exception e) {
log.error("Error initializing JCEF browser: ", e);
cleanup();
}
});
}
private void configureCefSettings(CefAppBuilder builder) {
CefSettings settings = builder.getCefSettings();
String basePath = InstallationPathConfig.getClientWebUIPath();
log.info("basePath " + basePath);
settings.cache_path = new File(basePath + "cache").getAbsolutePath();
settings.root_cache_path = new File(basePath + "root_cache").getAbsolutePath();
// settings.browser_subprocess_path = new File(basePath +
// "subprocess").getAbsolutePath();
// settings.resources_dir_path = new File(basePath + "resources").getAbsolutePath();
// settings.locales_dir_path = new File(basePath + "locales").getAbsolutePath();
settings.log_file = new File(basePath, "debug.log").getAbsolutePath();
settings.persist_session_cookies = true;
settings.windowless_rendering_enabled = false;
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;
builder.setAppHandler(
new MavenCefAppHandlerAdapter() {
@Override
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
log.info("CEF state changed: " + state);
if (state == CefApp.CefAppState.TERMINATED) {
System.exit(0);
}
}
});
}
private void setupDownloadHandler() {
client.addDownloadHandler(
new CefDownloadHandlerAdapter() {
@Override
public boolean onBeforeDownload(
CefBrowser browser,
CefDownloadItem downloadItem,
String suggestedName,
CefBeforeDownloadCallback callback) {
callback.Continue("", true);
return true;
}
@Override
public void onDownloadUpdated(
CefBrowser browser,
CefDownloadItem downloadItem,
CefDownloadItemCallback callback) {
if (downloadItem.isComplete()) {
log.info("Download completed: " + downloadItem.getFullPath());
} else if (downloadItem.isCanceled()) {
log.info("Download canceled: " + downloadItem.getFullPath());
}
}
});
}
private ConsoleProgressHandler createProgressHandler() {
return new ConsoleProgressHandler() {
@Override
public void handleProgress(EnumProgress state, float percent) {
Objects.requireNonNull(state, "state cannot be null");
SwingUtilities.invokeLater(
() -> {
if (loadingWindow != null) {
switch (state) {
case LOCATING:
loadingWindow.setStatus("Locating Files...");
loadingWindow.setProgress(0);
break;
case DOWNLOADING:
if (percent >= 0) {
loadingWindow.setStatus(
String.format(
"Downloading additional files: %.0f%%",
percent));
loadingWindow.setProgress((int) percent);
}
break;
case EXTRACTING:
loadingWindow.setStatus("Extracting files...");
loadingWindow.setProgress(60);
break;
case INITIALIZING:
loadingWindow.setStatus("Initializing UI...");
loadingWindow.setProgress(80);
break;
case INITIALIZED:
loadingWindow.setStatus("Finalising startup...");
loadingWindow.setProgress(90);
break;
}
}
});
}
};
}
private void setupMainFrame() {
frame = new JFrame("Stirling-PDF");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setUndecorated(true);
frame.setOpacity(0.0f);
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setDoubleBuffered(true);
contentPane.add(browser.getUIComponent(), BorderLayout.CENTER);
frame.setContentPane(contentPane);
frame.addWindowListener(
new java.awt.event.WindowAdapter() {
@Override
public void windowClosing(java.awt.event.WindowEvent windowEvent) {
cleanup();
System.exit(0);
}
});
frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800));
frame.setLocationRelativeTo(null);
loadIcon();
}
private void setupLoadHandler() {
final long initStartTime = System.currentTimeMillis();
log.info("Setting up load handler at: {}", initStartTime);
client.addLoadHandler(
new CefLoadHandlerAdapter() {
@Override
public void onLoadingStateChange(
CefBrowser browser,
boolean isLoading,
boolean canGoBack,
boolean canGoForward) {
log.debug(
"Loading state change - isLoading: {}, canGoBack: {}, canGoForward:"
+ " {}, browserInitialized: {}, Time elapsed: {}ms",
isLoading,
canGoBack,
canGoForward,
browserInitialized,
System.currentTimeMillis() - initStartTime);
if (!isLoading && !browserInitialized) {
log.info(
"Browser finished loading, preparing to initialize UI"
+ " components");
browserInitialized = true;
SwingUtilities.invokeLater(
() -> {
try {
if (loadingWindow != null) {
log.info("Starting UI initialization sequence");
// Close loading window first
loadingWindow.setVisible(false);
loadingWindow.dispose();
loadingWindow = null;
log.info("Loading window disposed");
// Then setup the main frame
frame.setVisible(false);
frame.dispose();
frame.setOpacity(1.0f);
frame.setUndecorated(false);
frame.pack();
frame.setSize(
UIScaling.scaleWidth(1280),
UIScaling.scaleHeight(800));
frame.setLocationRelativeTo(null);
log.debug("Frame reconfigured");
// Show the main frame
frame.setVisible(true);
frame.requestFocus();
frame.toFront();
log.info("Main frame displayed and focused");
// Focus the browser component
Timer focusTimer =
new Timer(
100,
e -> {
try {
browser.getUIComponent()
.requestFocus();
log.info(
"Browser component"
+ " focused");
} catch (Exception ex) {
log.error(
"Error focusing"
+ " browser",
ex);
}
});
focusTimer.setRepeats(false);
focusTimer.start();
}
} catch (Exception e) {
log.error("Error during UI initialization", e);
// Attempt cleanup on error
if (loadingWindow != null) {
loadingWindow.dispose();
loadingWindow = null;
}
if (frame != null) {
frame.setVisible(true);
frame.requestFocus();
}
}
});
}
}
});
}
private void setupTrayIcon(Image icon) {
if (!SystemTray.isSupported()) {
log.warn("System tray is not supported");
return;
}
try {
systemTray = SystemTray.getSystemTray();
// Create popup menu
PopupMenu popup = new PopupMenu();
// Create menu items
MenuItem showItem = new MenuItem("Show");
showItem.addActionListener(
e -> {
frame.setVisible(true);
frame.setState(Frame.NORMAL);
});
MenuItem exitItem = new MenuItem("Exit");
exitItem.addActionListener(
e -> {
cleanup();
System.exit(0);
});
// Add menu items to popup menu
popup.add(showItem);
popup.addSeparator();
popup.add(exitItem);
// Create tray icon
trayIcon = new TrayIcon(icon, "Stirling-PDF", popup);
trayIcon.setImageAutoSize(true);
// Add double-click behavior
trayIcon.addActionListener(
e -> {
frame.setVisible(true);
frame.setState(Frame.NORMAL);
});
// Add tray icon to system tray
systemTray.add(trayIcon);
// Modify frame behavior to minimize to tray
frame.addWindowStateListener(
new WindowStateListener() {
public void windowStateChanged(WindowEvent e) {
if (e.getNewState() == Frame.ICONIFIED) {
frame.setVisible(false);
}
}
});
} catch (AWTException e) {
log.error("Error setting up system tray icon", e);
}
}
private void loadIcon() {
try {
Image icon = null;
String[] iconPaths = {"/static/favicon.ico"};
for (String path : iconPaths) {
if (icon != null) break;
try {
try (InputStream is = getClass().getResourceAsStream(path)) {
if (is != null) {
icon = ImageIO.read(is);
break;
}
}
} catch (Exception e) {
log.debug("Could not load icon from " + path, e);
}
}
if (icon != null) {
frame.setIconImage(icon);
setupTrayIcon(icon);
} else {
log.warn("Could not load icon from any source");
}
} catch (Exception e) {
log.error("Error loading icon", e);
}
}
@PreDestroy
public void cleanup() {
if (browser != null) browser.close(true);
if (client != null) client.dispose();
if (cefApp != null) cefApp.dispose();
if (loadingWindow != null) loadingWindow.dispose();
}
public static void forceInitializeUI() {
try {
if (loadingWindow != null) {
log.info("Forcing start of UI initialization sequence");
// Close loading window first
loadingWindow.setVisible(false);
loadingWindow.dispose();
loadingWindow = null;
log.info("Loading window disposed");
// Then setup the main frame
frame.setVisible(false);
frame.dispose();
frame.setOpacity(1.0f);
frame.setUndecorated(false);
frame.pack();
frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800));
frame.setLocationRelativeTo(null);
log.debug("Frame reconfigured");
// Show the main frame
frame.setVisible(true);
frame.requestFocus();
frame.toFront();
log.info("Main frame displayed and focused");
// Focus the browser component if available
if (browser != null) {
Timer focusTimer =
new Timer(
100,
e -> {
try {
browser.getUIComponent().requestFocus();
log.info("Browser component focused");
} catch (Exception ex) {
log.error(
"Error focusing browser during force ui"
+ " initialization.",
ex);
}
});
focusTimer.setRepeats(false);
focusTimer.start();
}
}
} catch (Exception e) {
log.error("Error during Forced UI initialization.", e);
// Attempt cleanup on error
if (loadingWindow != null) {
loadingWindow.dispose();
loadingWindow = null;
}
if (frame != null) {
frame.setVisible(true);
frame.setOpacity(1.0f);
frame.setUndecorated(false);
frame.requestFocus();
}
}
}
}
@@ -1,351 +0,0 @@
package stirling.software.SPDF.UI.impl;
import java.awt.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import javax.swing.*;
import io.github.pixee.security.BoundedLineReader;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.util.UIScaling;
@Slf4j
public class LoadingWindow extends JDialog {
private final JProgressBar progressBar;
private final JLabel statusLabel;
private final JPanel mainPanel;
private final JLabel brandLabel;
private long startTime;
private Timer stuckTimer;
private long stuckThreshold = 4000;
private long timeAt90Percent = -1;
private volatile Process explorerProcess;
private static final boolean IS_WINDOWS =
System.getProperty("os.name").toLowerCase().contains("win");
public LoadingWindow(Frame parent, String initialUrl) {
super(parent, "Initializing Stirling-PDF", true);
startTime = System.currentTimeMillis();
log.info("Creating LoadingWindow - initialization started at: {}", startTime);
// Initialize components
mainPanel = new JPanel();
mainPanel.setBackground(Color.WHITE);
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 20, 30));
mainPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// Configure GridBagConstraints
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(5, 5, 5, 5);
gbc.weightx = 1.0;
gbc.weighty = 0.0;
// Add icon
try {
try (InputStream is = getClass().getResourceAsStream("/static/favicon.ico")) {
if (is != null) {
Image img = ImageIO.read(is);
if (img != null) {
Image scaledImg = UIScaling.scaleIcon(img, 48, 48);
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
gbc.gridy = 0;
mainPanel.add(iconLabel, gbc);
log.info("Icon loaded and scaled successfully");
}
}
}
} catch (Exception e) {
log.error("Failed to load icon", e);
}
// URL Label with explicit size
brandLabel = new JLabel(initialUrl);
brandLabel.setHorizontalAlignment(SwingConstants.CENTER);
brandLabel.setPreferredSize(new Dimension(300, 25));
brandLabel.setText("Stirling-PDF");
gbc.gridy = 1;
mainPanel.add(brandLabel, gbc);
// Status label with explicit size
statusLabel = new JLabel("Initializing...");
statusLabel.setHorizontalAlignment(SwingConstants.CENTER);
statusLabel.setPreferredSize(new Dimension(300, 25));
gbc.gridy = 2;
mainPanel.add(statusLabel, gbc);
// Progress bar with explicit size
progressBar = new JProgressBar(0, 100);
progressBar.setStringPainted(true);
progressBar.setPreferredSize(new Dimension(300, 25));
gbc.gridy = 3;
mainPanel.add(progressBar, gbc);
// Set dialog properties
setContentPane(mainPanel);
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
setResizable(false);
setUndecorated(false);
// Set size and position
setSize(UIScaling.scaleWidth(400), UIScaling.scaleHeight(200));
setLocationRelativeTo(parent);
setAlwaysOnTop(true);
setProgress(0);
setStatus("Starting...");
log.info(
"LoadingWindow initialization completed in {}ms",
System.currentTimeMillis() - startTime);
}
private void checkAndRefreshExplorer() {
if (!IS_WINDOWS) {
return;
}
if (timeAt90Percent == -1) {
timeAt90Percent = System.currentTimeMillis();
stuckTimer =
new Timer(
1000,
e -> {
long currentTime = System.currentTimeMillis();
if (currentTime - timeAt90Percent > stuckThreshold) {
try {
log.debug(
"Attempting Windows explorer refresh due to 90% stuck state");
String currentDir = System.getProperty("user.dir");
// Store current explorer PIDs before we start new one
Set<String> existingPids = new HashSet<>();
ProcessBuilder listExplorer =
new ProcessBuilder(
"cmd",
"/c",
"wmic",
"process",
"where",
"name='explorer.exe'",
"get",
"ProcessId",
"/format:csv");
Process process = listExplorer.start();
BufferedReader reader =
new BufferedReader(
new InputStreamReader(
process.getInputStream()));
String line;
while ((line =
BoundedLineReader.readLine(
reader, 5_000_000))
!= null) {
if (line.matches(".*\\d+.*")) { // Contains numbers
String[] parts = line.trim().split(",");
if (parts.length >= 2) {
existingPids.add(
parts[parts.length - 1].trim());
}
}
}
process.waitFor(2, TimeUnit.SECONDS);
// Start new explorer
ProcessBuilder pb =
new ProcessBuilder(
"cmd",
"/c",
"start",
"/min",
"/b",
"explorer.exe",
currentDir);
pb.redirectErrorStream(true);
explorerProcess = pb.start();
// Schedule cleanup
Timer cleanupTimer =
new Timer(
2000,
cleanup -> {
try {
// Find new explorer processes
ProcessBuilder findNewExplorer =
new ProcessBuilder(
"cmd",
"/c",
"wmic",
"process",
"where",
"name='explorer.exe'",
"get",
"ProcessId",
"/format:csv");
Process newProcess =
findNewExplorer.start();
BufferedReader newReader =
new BufferedReader(
new InputStreamReader(
newProcess
.getInputStream()));
String newLine;
while ((newLine =
BoundedLineReader
.readLine(
newReader,
5_000_000))
!= null) {
if (newLine.matches(
".*\\d+.*")) {
String[] parts =
newLine.trim()
.split(",");
if (parts.length >= 2) {
String pid =
parts[
parts.length
- 1]
.trim();
if (!existingPids
.contains(
pid)) {
log.debug(
"Found new explorer.exe with PID: "
+ pid);
ProcessBuilder
killProcess =
new ProcessBuilder(
"taskkill",
"/PID",
pid,
"/F");
killProcess
.redirectErrorStream(
true);
Process killResult =
killProcess
.start();
killResult.waitFor(
2,
TimeUnit
.SECONDS);
log.debug(
"Explorer process terminated: "
+ pid);
}
}
}
}
newProcess.waitFor(
2, TimeUnit.SECONDS);
} catch (Exception ex) {
log.error(
"Error cleaning up Windows explorer process",
ex);
}
});
cleanupTimer.setRepeats(false);
cleanupTimer.start();
stuckTimer.stop();
} catch (Exception ex) {
log.error("Error refreshing Windows explorer", ex);
}
}
});
stuckTimer.setRepeats(true);
stuckTimer.start();
}
}
public void setProgress(final int progress) {
SwingUtilities.invokeLater(
() -> {
try {
int validProgress = Math.min(Math.max(progress, 0), 100);
log.info(
"Setting progress to {}% at {}ms since start",
validProgress, System.currentTimeMillis() - startTime);
// Log additional details when near 90%
if (validProgress >= 85 && validProgress <= 95) {
log.info(
"Near 90% progress - Current status: {}, Window visible: {}, "
+ "Progress bar responding: {}, Memory usage: {}MB",
statusLabel.getText(),
isVisible(),
progressBar.isEnabled(),
Runtime.getRuntime().totalMemory() / (1024 * 1024));
// Add thread state logging
Thread currentThread = Thread.currentThread();
log.info(
"Current thread state - Name: {}, State: {}, Priority: {}",
currentThread.getName(),
currentThread.getState(),
currentThread.getPriority());
if (validProgress >= 90 && validProgress < 95) {
checkAndRefreshExplorer();
} else {
// Reset the timer if we move past 95%
if (validProgress >= 95) {
if (stuckTimer != null) {
stuckTimer.stop();
}
timeAt90Percent = -1;
}
}
}
progressBar.setValue(validProgress);
progressBar.setString(validProgress + "%");
mainPanel.revalidate();
mainPanel.repaint();
} catch (Exception e) {
log.error("Error updating progress to " + progress, e);
}
});
}
public void setStatus(final String status) {
log.info(
"Status update at {}ms - Setting status to: {}",
System.currentTimeMillis() - startTime,
status);
SwingUtilities.invokeLater(
() -> {
try {
String validStatus = status != null ? status : "";
statusLabel.setText(validStatus);
// Log UI state when status changes
log.info(
"UI State - Window visible: {}, Progress: {}%, Status: {}",
isVisible(), progressBar.getValue(), validStatus);
mainPanel.revalidate();
mainPanel.repaint();
} catch (Exception e) {
log.error("Error updating status to: " + status, e);
}
});
}
@Override
public void dispose() {
log.info("LoadingWindow disposing after {}ms", System.currentTimeMillis() - startTime);
super.dispose();
}
}
@@ -36,9 +36,15 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String requestURI = request.getRequestURI();
// Skip URL cleaning for API endpoints - they need their own parameter handling
if (requestURI.contains("/api/")) {
return true;
}
String queryString = request.getQueryString();
if (queryString != null && !queryString.isEmpty()) {
String requestURI = request.getRequestURI();
Map<String, String> allowedParameters = new HashMap<>();
// Keep only the allowed parameters
@@ -238,14 +238,14 @@ public class EndpointConfiguration {
addEndpointToGroup("PageOps", "rotate-pdf");
addEndpointToGroup("PageOps", "multi-page-layout");
addEndpointToGroup("PageOps", "scale-pages");
addEndpointToGroup("PageOps", "adjust-contrast");
addEndpointToGroup("PageOps", "crop");
addEndpointToGroup("PageOps", "auto-split-pdf");
addEndpointToGroup("PageOps", "extract-page");
addEndpointToGroup("PageOps", "pdf-to-single-page");
addEndpointToGroup("PageOps", "auto-split-pdf");
addEndpointToGroup("PageOps", "split-by-size-or-count");
addEndpointToGroup("PageOps", "overlay-pdf");
addEndpointToGroup("PageOps", "split-pdf-by-sections");
addEndpointToGroup("PageOps", "split-pdf-by-chapters");
// Adding endpoints to "Convert" group
addEndpointToGroup("Convert", "pdf-to-img");
@@ -274,27 +274,43 @@ public class EndpointConfiguration {
addEndpointToGroup("Security", "sanitize-pdf");
addEndpointToGroup("Security", "auto-redact");
addEndpointToGroup("Security", "redact");
addEndpointToGroup("Security", "validate-signature");
addEndpointToGroup("Security", "stamp");
addEndpointToGroup("Security", "sign");
// Adding endpoints to "Other" group
addEndpointToGroup("Other", "ocr-pdf");
addEndpointToGroup("Other", "add-image");
addEndpointToGroup("Other", "compress-pdf");
addEndpointToGroup("Other", "extract-images");
addEndpointToGroup("Other", "change-metadata");
addEndpointToGroup("Other", "extract-image-scans");
addEndpointToGroup("Other", "sign");
addEndpointToGroup("Other", "flatten");
addEndpointToGroup("Other", "repair");
addEndpointToGroup("Other", "unlock-pdf-forms");
addEndpointToGroup("Other", REMOVE_BLANKS);
addEndpointToGroup("Other", "remove-annotations");
addEndpointToGroup("Other", "compare");
addEndpointToGroup("Other", "add-page-numbers");
addEndpointToGroup("Other", "auto-rename");
addEndpointToGroup("Other", "get-info-on-pdf");
addEndpointToGroup("Other", "show-javascript");
addEndpointToGroup("Other", "remove-image-pdf");
addEndpointToGroup("Other", "add-attachments");
addEndpointToGroup("Other", "view-pdf");
addEndpointToGroup("Other", "replace-and-invert-color-pdf");
addEndpointToGroup("Other", "multi-tool");
// Adding endpoints to "Advance" group
addEndpointToGroup("Advance", "adjust-contrast");
addEndpointToGroup("Advance", "compress-pdf");
addEndpointToGroup("Advance", "extract-image-scans");
addEndpointToGroup("Advance", "repair");
addEndpointToGroup("Advance", "auto-rename");
addEndpointToGroup("Advance", "pipeline");
addEndpointToGroup("Advance", "scanner-effect");
addEndpointToGroup("Advance", "auto-split-pdf");
addEndpointToGroup("Advance", "show-javascript");
addEndpointToGroup("Advance", "split-by-size-or-count");
addEndpointToGroup("Advance", "overlay-pdf");
addEndpointToGroup("Advance", "split-pdf-by-sections");
addEndpointToGroup("Advance", "edit-table-of-contents");
addEndpointToGroup("Advance", "split-pdf-by-chapters");
// CLI
addEndpointToGroup("CLI", "compress-pdf");
@@ -405,7 +421,6 @@ public class EndpointConfiguration {
// file-to-pdf has multiple implementations
addEndpointAlternative("file-to-pdf", "LibreOffice");
addEndpointAlternative("file-to-pdf", "Python");
addEndpointAlternative("file-to-pdf", "Unoconvert");
// pdf-to-html and pdf-to-markdown can use either LibreOffice or Pdftohtml
@@ -35,6 +35,7 @@ public class InitialSetup {
initEnableCSRFSecurity();
initLegalUrls();
initSetAppVersion();
GeneralUtils.extractPipeline();
}
public void initUUIDKey() throws IOException {
@@ -10,6 +10,7 @@ import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import lombok.RequiredArgsConstructor;
@@ -50,17 +51,25 @@ public class OpenApiConfig {
.url("https://www.stirlingpdf.com")
.email("contact@stirlingpdf.com"))
.description(DEFAULT_DESCRIPTION);
OpenAPI openAPI = new OpenAPI().info(info);
// Add server configuration from environment variable
String swaggerServerUrl = System.getenv("SWAGGER_SERVER_URL");
if (swaggerServerUrl != null && !swaggerServerUrl.trim().isEmpty()) {
Server server = new Server().url(swaggerServerUrl).description("API Server");
openAPI.addServersItem(server);
}
if (!applicationProperties.getSecurity().getEnableLogin()) {
return new OpenAPI().components(new Components()).info(info);
return openAPI.components(new Components());
} else {
SecurityScheme apiKeyScheme =
new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-API-KEY");
return new OpenAPI()
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
.info(info)
return openAPI.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
.addSecurityItem(new SecurityRequirement().addList("apiKey"));
}
}
@@ -2,13 +2,10 @@ package stirling.software.SPDF.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import lombok.RequiredArgsConstructor;
import stirling.software.common.configuration.InstallationPathConfig;
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
@@ -20,12 +17,12 @@ public class WebMvcConfig implements WebMvcConfigurer {
registry.addInterceptor(endpointInterceptor);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Handler for external static resources
registry.addResourceHandler("/**")
.addResourceLocations(
"file:" + InstallationPathConfig.getStaticPath(), "classpath:/static/");
// .setCachePeriod(0); // Optional: disable caching
}
// @Override
// public void addResourceHandlers(ResourceHandlerRegistry registry) {
// // Handler for external static resources - DISABLED in backend-only mode
// registry.addResourceHandler("/**")
// .addResourceLocations(
// "file:" + InstallationPathConfig.getStaticPath(), "classpath:/static/");
// // .setCachePeriod(0); // Optional: disable caching
// }
}
@@ -18,6 +18,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
@@ -29,7 +30,7 @@ public class AnalysisController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@PostMapping(value = "/page-count", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/page-count", consumes = "multipart/form-data")
@Operation(
summary = "Get PDF page count",
description = "Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO")
@@ -39,7 +40,7 @@ public class AnalysisController {
}
}
@PostMapping(value = "/basic-info", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/basic-info", consumes = "multipart/form-data")
@Operation(
summary = "Get basic PDF information",
description = "Returns page count, version, file size. Input:PDF Output:JSON Type:SISO")
@@ -53,7 +54,7 @@ public class AnalysisController {
}
}
@PostMapping(value = "/document-properties", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/document-properties", consumes = "multipart/form-data")
@Operation(
summary = "Get PDF document properties",
description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO")
@@ -76,7 +77,7 @@ public class AnalysisController {
}
}
@PostMapping(value = "/page-dimensions", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/page-dimensions", consumes = "multipart/form-data")
@Operation(
summary = "Get page dimensions for all pages",
description = "Returns width and height of each page. Input:PDF Output:JSON Type:SISO")
@@ -96,7 +97,7 @@ public class AnalysisController {
}
}
@PostMapping(value = "/form-fields", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/form-fields", consumes = "multipart/form-data")
@Operation(
summary = "Get form field information",
description =
@@ -119,7 +120,7 @@ public class AnalysisController {
}
}
@PostMapping(value = "/annotation-info", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/annotation-info", consumes = "multipart/form-data")
@Operation(
summary = "Get annotation information",
description = "Returns count and types of annotations. Input:PDF Output:JSON Type:SISO")
@@ -143,7 +144,7 @@ public class AnalysisController {
}
}
@PostMapping(value = "/font-info", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/font-info", consumes = "multipart/form-data")
@Operation(
summary = "Get font information",
description =
@@ -165,7 +166,7 @@ public class AnalysisController {
}
}
@PostMapping(value = "/security-info", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/security-info", consumes = "multipart/form-data")
@Operation(
summary = "Get security information",
description =
@@ -12,7 +12,6 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
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;
@@ -22,6 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.general.CropPdfForm;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@@ -33,7 +33,7 @@ public class CropController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@PostMapping(value = "/crop", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/crop", consumes = "multipart/form-data")
@Operation(
summary = "Crops a PDF document",
description =
@@ -14,7 +14,6 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@@ -31,6 +30,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.EditTableOfContentsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@@ -44,7 +44,7 @@ public class EditTableOfContentsController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final ObjectMapper objectMapper;
@PostMapping(value = "/extract-bookmarks", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/extract-bookmarks", consumes = "multipart/form-data")
@Operation(
summary = "Extract PDF Bookmarks",
description = "Extracts bookmarks/table of contents from a PDF document as JSON.")
@@ -152,7 +152,7 @@ public class EditTableOfContentsController {
return bookmark;
}
@PostMapping(value = "/edit-table-of-contents", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/edit-table-of-contents", consumes = "multipart/form-data")
@Operation(
summary = "Edit Table of Contents",
description = "Add or edit bookmarks/table of contents in a PDF document.")
@@ -22,7 +22,6 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -34,6 +33,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
@@ -154,7 +154,7 @@ public class MergeController {
}
}
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
@Operation(
summary = "Merge multiple PDF files into one",
description =
@@ -13,7 +13,6 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -25,6 +24,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@@ -36,7 +36,7 @@ public class MultiPageLayoutController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
@Operation(
summary = "Merge multiple pages of a PDF document into a single page",
description =
@@ -6,7 +6,6 @@ import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
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;
@@ -16,6 +15,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.service.PdfImageRemovalService;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@@ -46,7 +46,7 @@ public class PdfImageRemovalController {
* content type and filename.
* @throws IOException If an error occurs while processing the PDF file.
*/
@PostMapping(consumes = "multipart/form-data", value = "/remove-image-pdf")
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-image-pdf")
@Operation(
summary = "Remove images from file to reduce the file size.",
description =
@@ -15,7 +15,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -27,6 +26,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.WebResponseUtils;
@@ -39,7 +39,7 @@ public class PdfOverlayController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
@Operation(
summary = "Overlay PDF files in various modes",
description =
@@ -9,7 +9,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -24,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.SortTypes;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
@@ -38,7 +38,7 @@ public class RearrangePagesPDFController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-pages")
@Operation(
summary = "Remove pages from a PDF file",
description =
@@ -237,7 +237,7 @@ public class RearrangePagesPDFController {
}
}
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
@Operation(
summary = "Rearrange pages in a PDF file",
description =
@@ -7,7 +7,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -19,6 +18,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.general.RotatePDFRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@@ -31,7 +31,7 @@ public class RotationController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
@Operation(
summary = "Rotate a PDF file",
description =
@@ -14,7 +14,6 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -26,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.general.ScalePagesRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@@ -38,7 +38,7 @@ public class ScalePagesController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/scale-pages", consumes = "multipart/form-data")
@Operation(
summary = "Change the size of a PDF page/document",
description =
@@ -7,7 +7,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -17,6 +16,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils;
@@ -31,7 +31,7 @@ public class SettingsController {
private final ApplicationProperties applicationProperties;
private final EndpointConfiguration endpointConfiguration;
@PostMapping("/update-enable-analytics")
@AutoJobPostMapping("/update-enable-analytics")
@Hidden
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
if (applicationProperties.getSystem().getEnableAnalytics() != null) {
@@ -15,7 +15,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -28,6 +27,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;
@@ -41,7 +41,7 @@ public class SplitPDFController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/split-pages")
@Operation(
summary = "Split a PDF file into separate documents",
description =
@@ -15,7 +15,6 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -32,6 +31,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.model.PdfMetadata;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.service.PdfMetadataService;
@@ -117,7 +117,7 @@ public class SplitPdfByChaptersController {
return bookmarks;
}
@PostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
@Operation(
summary = "Split PDFs by Chapters",
description = "Splits a PDF into chapters and returns a ZIP file.")
@@ -20,7 +20,6 @@ import org.apache.pdfbox.util.Matrix;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@@ -32,6 +31,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.WebResponseUtils;
@@ -43,7 +43,7 @@ public class SplitPdfBySectionsController {
private final CustomPDFDocumentFactory pdfDocumentFactory;
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
@AutoJobPostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
@Operation(
summary = "Split PDF pages into smaller sections",
description =

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