Compare commits

...

397 Commits

Author SHA1 Message Date
Anthony Stirling
a4ddb5f1af Update build.yml 2024-12-25 21:44:34 +00:00
Anthony Stirling
d4882df837 Update build.yml 2024-12-25 21:44:11 +00:00
Anthony Stirling
9884c65b10 formattingand autowired constructors (#2557)
# Description
This pull request includes several changes aimed at improving the code
structure and removing redundant code. The most significant changes
involve reordering methods, removing unnecessary annotations, and
refactoring constructors to use dependency injection.
Autowired now comes via constructor (which also doesn't need autowired
annotation as its done by default for configuration)



## Checklist

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my own code
- [ ] I have attached images of the change if it is UI based
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated
relevant docs on [Stirling-PDFs doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [ ] My changes generate no new warnings
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
2024-12-24 09:52:53 +00:00
Ludy
2abfe599ed Fix: duplicated mapping key (#2550)
# Description

Please provide a summary of the changes, including relevant motivation
and context.

Closes #(issue_number)

## Checklist

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my own code
- [ ] I have attached images of the change if it is UI based
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated
relevant docs on [Stirling-PDFs doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [ ] My changes generate no new warnings
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
2024-12-22 16:51:22 +00:00
Ludy
cbdec6ec25 rollback check_properties.yml (#2551)
# Description

Please provide a summary of the changes, including relevant motivation
and context.

Closes #(issue_number)

## Checklist

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have performed a self-review of my own code
- [ ] I have attached images of the change if it is UI based
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated
relevant docs on [Stirling-PDFs doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [ ] My changes generate no new warnings
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
2024-12-22 16:50:42 +00:00
Ludy
d4f25db647 used pre-commit/action (#2548)
# Description

Please provide a summary of the changes, including relevant motivation
and context.

Closes #(issue_number)

## Checklist

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my own code
- [ ] I have attached images of the change if it is UI based
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated
relevant docs on [Stirling-PDFs doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [ ] My changes generate no new warnings
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
2024-12-22 16:36:46 +00:00
Ludy
0f1b94303f add label Security to changed files (#2547)
# Description

Please provide a summary of the changes, including relevant motivation
and context.

Closes #(issue_number)

## Checklist

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my own code
- [ ] I have attached images of the change if it is UI based
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] If my code has heavily changed functionality I have updated
relevant docs on [Stirling-PDFs doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
- [ ] My changes generate no new warnings
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
2024-12-22 15:49:32 +00:00
Ludy
4dfdf2d8ec Update pre_commit.yml (#2546) 2024-12-22 15:02:23 +00:00
Anthony Stirling
d6937b1784 Merge pull request #2543 from Ludy87/add_pre_commit
Introduces pre-commit github-action
2024-12-22 13:45:03 +00:00
Anthony Stirling
3ae732352e Merge pull request #2541 from Stirling-Tools/docker-rename
Docker rename for standardisation
2024-12-22 13:44:21 +00:00
Anthony Stirling
f9b90692fb Merge pull request #2542 from Ludy87/fix_upload_artifact
Rollback action-gh-release version from v2.2.0 to v2.1.0
2024-12-22 13:43:23 +00:00
a
04f72f151d Merge branch 'docker-rename' of git@github.com:Stirling-Tools/Stirling-PDF.git into docker-rename 2024-12-22 13:37:51 +00:00
Anthony Stirling
73ee9651a3 3.21.0 py3-opencv requires python 4.10 which means broken 2024-12-22 13:37:41 +00:00
Anthony Stirling
a0f3d7b7d4 Update push-docker.yml 2024-12-22 12:41:53 +00:00
Ludy87
7223b379e7 Introduces pre-commit github-action
https://github.com/Stirling-Tools/Stirling-PDF/blob/main/.pre-commit-config.yaml
2024-12-22 13:36:26 +01:00
Ludy87
e833d688e7 Update multiOSReleases.yml 2024-12-22 13:13:24 +01:00
Ludy87
74cab98a1b Rollback action-gh-release version from v2.2.0 to v2.1.0
https://github.com/softprops/action-gh-release/issues/556
2024-12-22 13:12:48 +01:00
Anthony Stirling
5c1abdd49e fix 2024-12-22 12:00:52 +00:00
Anthony Stirling
341a234b7c quick fix 2024-12-22 11:55:40 +00:00
Anthony Stirling
34ddc298cf test 2024-12-22 11:48:06 +00:00
Anthony Stirling
f4ae4ac5c4 reremove 2024-12-22 11:20:46 +00:00
Anthony Stirling
91aece2284 Merge remote-tracking branch 'origin/main' into docker-rename 2024-12-22 11:20:19 +00:00
Anthony Stirling
2a1dad80f0 rename 2024-12-22 11:17:22 +00:00
Anthony Stirling
f1bf42544d Merge pull request #2540 from PeterDaveHelloKitchen/Dockerfile
Standardize Alpine base image format and version across all Dockerfiles
2024-12-22 11:16:23 +00:00
Anthony Stirling
2fd8d8809b Merge pull request #2535 from Stirling-Tools/cosign
Update push-docker.yml with cosign
2024-12-22 10:52:53 +00:00
Anthony Stirling
b2628d4c3f Merge pull request #2536 from Stirling-Tools/docker-labels
Docker labels
2024-12-22 10:52:48 +00:00
Anthony Stirling
50fb665c29 Merge pull request #2537 from Ludy87/bump_create_pull_request_7_0_5
Update GitHub Actions: Upgrade create-pull-request and Enhance Workflow Configurations
2024-12-22 10:51:47 +00:00
Peter Dave Hello
b0881cdb4c Standardize Alpine base image format and version across all Dockerfiles
- Update all Dockerfiles to use `alpine:3.21.0` with SHA256 checksum
- Ensure consistent image versioning, enhanced security, and
  reproducibility by explicitly specifying the digest.

cc #2436 #2516
2024-12-22 18:10:49 +08:00
Anthony Stirling
988a877199 Merge pull request #2538 from PeterDaveHelloKitchen/ImproveDocs
Improve Markdown documentation a bit
2024-12-22 09:56:51 +00:00
Peter Dave Hello
80f53e972a Improve Markdown documentation a bit 2024-12-22 17:37:58 +08:00
Ludy87
6697cccfa9 Bump peter-evans/create-pull-request from 6.1.0 to 7.0.5 2024-12-22 01:41:45 +01:00
Anthony Stirling
9820a3276d Update Dockerfile 2024-12-22 00:11:06 +00:00
Anthony Stirling
fb6cefa301 Update Dockerfile-ultra-lite 2024-12-22 00:10:48 +00:00
Anthony Stirling
974b947fc3 Update Dockerfile-fat 2024-12-22 00:10:31 +00:00
Anthony Stirling
4e45bae856 Update Dockerfile 2024-12-22 00:09:52 +00:00
Anthony Stirling
74c6ea7c60 Merge branch 'main' into cosign 2024-12-21 23:50:29 +00:00
Anthony Stirling
15abef1b82 Update push-docker.yml 2024-12-21 23:49:30 +00:00
Anthony Stirling
fb61fea3e4 Merge pull request #2534 from Ludy87/token_permissions_2
[Security] Token permissions no. 2
2024-12-21 23:41:37 +00:00
Ludy87
f29d4db31e [Security] Token permissions no. 2 2024-12-22 00:33:41 +01:00
Anthony Stirling
b86eac20ea Merge pull request #2533 from Ludy87/token_permissions
[Security] Token permissions
2024-12-21 23:03:10 +00:00
Ludy87
7ccb9db9f9 Update update-translations.yml 2024-12-21 23:48:09 +01:00
Ludy87
e920eb0555 Update check_properties.yml 2024-12-21 23:48:02 +01:00
Ludy87
822ccbabdf Update build.yml 2024-12-21 23:47:53 +01:00
Ludy87
0d2b5f605f Permission Position moved to top level 2024-12-21 23:40:53 +01:00
Anthony Stirling
76924ccd2f Merge pull request #2532 from Ludy87/fix_Dangerous_Workflow_4
[Security] Dangerous-Workflow
2024-12-21 22:17:57 +00:00
Ludy87
12e8aceb4c [Security] Dangerous-Workflow
https://github.com/Ludy87/test_java/security/code-scanning/26
2024-12-21 23:15:24 +01:00
Anthony Stirling
46261c55b5 Merge pull request #2527 from Ludy87/fix_pre_commit_config_2
Update .pre-commit-config.yaml no. 2
2024-12-21 17:36:13 +00:00
Ludy
d2e3c2a12c Merge branch 'main' into fix_pre_commit_config_2 2024-12-21 18:03:05 +01:00
Anthony Stirling
58bcb9fdb1 Merge pull request #2531 from Ludy87/fix_dangerous_workflow_3
Update check_properties.yml no. 3
2024-12-21 16:52:58 +00:00
Ludy87
ae44c4c723 Update check_properties.yml 2024-12-21 17:50:42 +01:00
Anthony Stirling
a7f900b959 Merge pull request #2529 from Ludy87/remove_duplicate_permission
Removing duplicate permissions
2024-12-21 16:35:11 +00:00
Anthony Stirling
c0873050a3 Merge pull request #2530 from Ludy87/fix_dangerous_workflow_2
Update check_properties.yml no. 2
2024-12-21 16:18:33 +00:00
Ludy87
f09e70218d Update check_properties.yml 2024-12-21 17:17:26 +01:00
Ludy
e0d37d2e10 Merge branch 'main' into remove_duplicate_permission 2024-12-21 17:06:27 +01:00
Ludy87
70cc03fc63 Removing duplicate permissions 2024-12-21 17:05:45 +01:00
Anthony Stirling
4ee756f7dd Merge pull request #2528 from Ludy87/fix_dangerous_workflow
Update check_properties.yml
2024-12-21 16:03:33 +00:00
Ludy87
67a08a7c05 Update check_properties.yml 2024-12-21 16:59:33 +01:00
Anthony Stirling
d136f9bd31 Update README.md 2024-12-21 15:55:47 +00:00
Anthony Stirling
a909592533 Update CODEOWNERS 2024-12-21 15:01:16 +00:00
Ludy
6a609712ba Merge branch 'main' into fix_pre_commit_config_2 2024-12-21 15:36:58 +01:00
Anthony Stirling
238db1aaff Update scorecards.yml 2024-12-21 14:34:29 +00:00
Ludy87
c08329ec48 Update .pre-commit-config.yaml no. 2 2024-12-21 15:34:07 +01:00
Anthony Stirling
1defa441d6 Update scorecards.yml 2024-12-21 14:31:25 +00:00
Anthony Stirling
51577a0a69 Merge pull request #2514 from Stirling-Tools/sync_readme
📝 Update README: Translation Progress Table
2024-12-21 13:59:30 +00:00
Anthony Stirling
0366bc6bf9 Merge pull request #2526 from Stirling-Tools/update-3rd-party-licenses
Update 3rd Party Licenses
2024-12-21 13:59:20 +00:00
GitHub Action
b6c66c47cd Update 3rd Party Licenses
Signed-off-by: GitHub Action <action@github.com>
2024-12-21 13:53:33 +00:00
github-actions[bot]
de6bfa2af2 📝 Sync README
> Made via sync_files.yml
2024-12-21 13:52:55 +00:00
Anthony Stirling
2dd580cf57 Merge pull request #2520 from Stirling-Tools/dependabot/github_actions/ossf/scorecard-action-2.4.0
Bump ossf/scorecard-action from 2.3.3 to 2.4.0
2024-12-21 13:52:47 +00:00
Anthony Stirling
5df445eabf Merge pull request #2524 from Stirling-Tools/dependabot/gradle/org.springframework.boot-3.4.1
Bump org.springframework.boot from 3.4.0 to 3.4.1
2024-12-21 13:52:32 +00:00
Anthony Stirling
d3e0c0bce1 Merge branch 'main' into dependabot/gradle/org.springframework.boot-3.4.1 2024-12-21 13:52:21 +00:00
Anthony Stirling
5c0a606d66 Merge pull request #2517 from Stirling-Tools/dependabot/docker/gradle-8.12-jdk17
Bump gradle from 8.11-jdk17 to 8.12-jdk17
2024-12-21 13:51:15 +00:00
dependabot[bot]
226cbe1a8e Bump ossf/scorecard-action from 2.3.3 to 2.4.0
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.3 to 2.4.0.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](dc50aa9510...62b2cac7ed)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-21 13:51:06 +00:00
Anthony Stirling
f13945b05f Merge pull request #2523 from Stirling-Tools/dependabot/gradle/io.spring.dependency-management-1.1.7
Bump io.spring.dependency-management from 1.1.6 to 1.1.7
2024-12-21 13:50:46 +00:00
Anthony Stirling
889b612cf3 Merge pull request #2522 from Stirling-Tools/dependabot/gradle/springBootVersion-3.4.1
Bump springBootVersion from 3.4.0 to 3.4.1
2024-12-21 13:50:38 +00:00
Anthony Stirling
9aec9d3451 Merge pull request #2521 from Stirling-Tools/dependabot/github_actions/softprops/action-gh-release-2.2.0
Bump softprops/action-gh-release from 2.0.9 to 2.2.0
2024-12-21 13:50:27 +00:00
Anthony Stirling
b65ac4032e Merge pull request #2519 from Stirling-Tools/dependabot/github_actions/crazy-max/ghaction-github-labeler-5.1.0
Bump crazy-max/ghaction-github-labeler from 5.0.0 to 5.1.0
2024-12-21 13:50:18 +00:00
dependabot[bot]
e7f2576857 Bump softprops/action-gh-release from 2.0.9 to 2.2.0
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.9 to 2.2.0.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](e7a8f85e1c...7b4da11513)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-21 13:02:26 +00:00
dependabot[bot]
c877f122c4 Bump crazy-max/ghaction-github-labeler from 5.0.0 to 5.1.0
Bumps [crazy-max/ghaction-github-labeler](https://github.com/crazy-max/ghaction-github-labeler) from 5.0.0 to 5.1.0.
- [Release notes](https://github.com/crazy-max/ghaction-github-labeler/releases)
- [Commits](de749cf181...b54af0c258)

---
updated-dependencies:
- dependency-name: crazy-max/ghaction-github-labeler
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-21 13:02:23 +00:00
Anthony Stirling
ff99c46434 Rename codeql.yml to codeql.yml-disabled 2024-12-21 13:01:19 +00:00
Anthony Stirling
8f5c5211d1 Merge pull request #2525 from Ludy87/fix_pre_commit_config
Update .pre-commit-config.yaml
2024-12-21 12:54:05 +00:00
Anthony Stirling
125bebf9ff Merge branch 'main' into dependabot/gradle/org.springframework.boot-3.4.1 2024-12-21 12:52:46 +00:00
Anthony Stirling
dd03875ece Merge branch 'main' into dependabot/gradle/io.spring.dependency-management-1.1.7 2024-12-21 12:52:40 +00:00
Anthony Stirling
6456a28436 Merge branch 'main' into dependabot/gradle/springBootVersion-3.4.1 2024-12-21 12:52:32 +00:00
Anthony Stirling
1bfa534dcd Update gradle-wrapper.properties 2024-12-21 12:52:14 +00:00
Ludy87
7b78c5cbf1 Update .pre-commit-config.yaml 2024-12-21 13:50:03 +01:00
Anthony Stirling
adb715f2f2 Update codeql.yml 2024-12-21 12:45:41 +00:00
Anthony Stirling
b3230f1e52 Merge branch 'main' into dependabot/gradle/springBootVersion-3.4.1 2024-12-21 12:43:52 +00:00
Anthony Stirling
e5d62042c4 Merge branch 'main' into dependabot/gradle/org.springframework.boot-3.4.1 2024-12-21 12:41:16 +00:00
Anthony Stirling
801a2a792b Update codeql.yml 2024-12-21 12:40:33 +00:00
dependabot[bot]
b8466c2b97 Bump org.springframework.boot from 3.4.0 to 3.4.1
Bumps [org.springframework.boot](https://github.com/spring-projects/spring-boot) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

---
updated-dependencies:
- dependency-name: org.springframework.boot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-21 12:38:15 +00:00
dependabot[bot]
1006aa110e Bump io.spring.dependency-management from 1.1.6 to 1.1.7
Bumps [io.spring.dependency-management](https://github.com/spring-gradle-plugins/dependency-management-plugin) from 1.1.6 to 1.1.7.
- [Release notes](https://github.com/spring-gradle-plugins/dependency-management-plugin/releases)
- [Commits](https://github.com/spring-gradle-plugins/dependency-management-plugin/compare/v1.1.6...v1.1.7)

---
updated-dependencies:
- dependency-name: io.spring.dependency-management
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-21 12:38:00 +00:00
dependabot[bot]
daae6bfd3e Bump springBootVersion from 3.4.0 to 3.4.1
Bumps `springBootVersion` from 3.4.0 to 3.4.1.

Updates `org.springframework.boot:spring-boot-starter-web` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

Updates `org.springframework.boot:spring-boot-starter-jetty` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

Updates `org.springframework.boot:spring-boot-starter-thymeleaf` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

Updates `org.springframework.boot:spring-boot-starter-security` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

Updates `org.springframework.boot:spring-boot-starter-data-jpa` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

Updates `org.springframework.boot:spring-boot-starter-oauth2-client` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

Updates `org.springframework.session:spring-session-core` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-session/releases)
- [Changelog](https://github.com/spring-projects/spring-session/blob/main/RELEASE.adoc)
- [Commits](https://github.com/spring-projects/spring-session/compare/3.4.0...3.4.1)

Updates `org.springframework.boot:spring-boot-starter-test` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

Updates `org.springframework.boot:spring-boot-starter-actuator` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

Updates `org.springframework.boot:spring-boot-devtools` from 3.4.0 to 3.4.1
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.0...v3.4.1)

---
updated-dependencies:
- dependency-name: org.springframework.boot:spring-boot-starter-web
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.springframework.boot:spring-boot-starter-jetty
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.springframework.boot:spring-boot-starter-thymeleaf
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.springframework.boot:spring-boot-starter-security
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.springframework.boot:spring-boot-starter-data-jpa
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.springframework.boot:spring-boot-starter-oauth2-client
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.springframework.session:spring-session-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.springframework.boot:spring-boot-starter-test
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.springframework.boot:spring-boot-starter-actuator
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.springframework.boot:spring-boot-devtools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-21 12:37:52 +00:00
dependabot[bot]
1795d5764a Bump gradle from 8.11-jdk17 to 8.12-jdk17
Bumps gradle from 8.11-jdk17 to 8.12-jdk17.

---
updated-dependencies:
- dependency-name: gradle
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-21 12:37:29 +00:00
Anthony Stirling
9dcb3009f0 Merge pull request #2516 from step-security-bot/stepsecurity_remediation_1734784111
[StepSecurity] Apply security best practices
2024-12-21 12:36:49 +00:00
Anthony Stirling
ca9abf76be Update .pre-commit-config.yaml 2024-12-21 12:35:38 +00:00
Anthony Stirling
3870f73949 Update dependabot.yml 2024-12-21 12:30:44 +00:00
StepSecurity Bot
dc5b214932 [StepSecurity] Apply security best practices
Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
2024-12-21 12:28:35 +00:00
Anthony Stirling
bba3d65368 Create SECURITY.md 2024-12-21 00:23:58 +00:00
Anthony Stirling
e3d2bd3b1b Merge pull request #2513 from PeterDaveHelloKitchen/zh_TW
Update and improve zh_TW Traditional Chinese locale
2024-12-20 17:05:36 +00:00
Anthony Stirling
a71cb7f5d9 Merge pull request #2509 from Ludy87/btn_standardize
standardize the submit button
2024-12-20 10:24:49 +00:00
Ludy87
a9e250547a standardize the submit button 2024-12-20 11:08:57 +01:00
Anthony Stirling
30bcdbd7be Merge pull request #2507 from Ludy87/remove_redundant_entries
remove redundant entries
2024-12-20 09:13:28 +00:00
Ludy87
61603aebb2 remove redundant entries 2024-12-20 09:59:12 +01:00
Peter Dave Hello
26340626df Update and improve zh_TW Traditional Chinese locale 2024-12-20 03:55:31 +08:00
Anthony Stirling
8dca4a588d Merge pull request #2505 from reecebrowne/toolbar-tweaks2
Toolbar tweaks2
2024-12-19 13:22:41 +00:00
Reece Browne
63386baa0d Conditional logic for stacking columns 2024-12-19 12:58:57 +00:00
Reece Browne
2a93910da3 Remove padding 2024-12-19 12:04:08 +00:00
Reece Browne
fca6dc1fd8 Stack convert section, don't remove. Move remove into own js 2024-12-19 11:59:58 +00:00
Anthony Stirling
2d82c5fa77 Merge pull request #2503 from omar-ahmed42/fix-page-resize-in-redact-img-conversion
Fix: Draw image with the original PDF page dimensions
2024-12-19 11:49:18 +00:00
Omar Ahmed Hassan
167c792bf0 Draw image with the original PDF page dimensions 2024-12-19 13:34:13 +02:00
Anthony Stirling
95a9e10dc8 Update releaseArtifacts.yml 2024-12-19 10:52:41 +00:00
Anthony Stirling
4d6368048c Update build.gradle 2024-12-18 21:28:23 +00:00
Anthony Stirling
4e715a82e0 Merge pull request #2501 from Ludy87/missing_pdflib
[Bugfix] A variety of tools miss PDFLib
2024-12-18 21:12:08 +00:00
Ludy87
164381e940 some tools lack PDFLib 2024-12-18 21:03:08 +01:00
Anthony Stirling
0436f45de5 Merge pull request #2500 from Stirling-Tools/configCheck
Config mount check
2024-12-18 18:14:46 +00:00
Anthony Stirling
6ce761aff2 Merge remote-tracking branch 'origin/main' into configCheck 2024-12-18 18:05:16 +00:00
Anthony Stirling
8a5d9f9a95 mounted_config_dir 2024-12-18 18:04:10 +00:00
Anthony Stirling
c0ef624a1d Merge pull request #2499 from albanobattistella/patch-62
Update messages_it_IT.properties
2024-12-18 14:42:48 +00:00
albanobattistella
b9ae90274f Update messages_it_IT.properties 2024-12-18 15:34:39 +01:00
Anthony Stirling
764b8f4d22 Merge pull request #2498 from Ludy87/code_refactoring
Exclude Sensitive and Error-Prone Fields from toString() in SAML2 Con…
2024-12-18 12:58:13 +00:00
Ludy87
a531f53893 Exclude Sensitive and Error-Prone Fields from toString() in SAML2 Configuration 2024-12-18 13:40:24 +01:00
Anthony Stirling
74d6d96f4e Merge pull request #2495 from Stirling-Tools/sync_readme
📝 Update README: Translation Progress Table
2024-12-18 12:18:17 +00:00
Anthony Stirling
1862ab1671 Merge pull request #2497 from reecebrowne/File-input-append
Remove file input append
2024-12-18 12:18:02 +00:00
Reece Browne
bf95ca43dc Remove append entirely 2024-12-18 11:47:23 +00:00
Reece Browne
678ef85da1 Don't remove files from file input, just append 2024-12-18 11:09:52 +00:00
Anthony Stirling
9a6afdd921 Merge pull request #2493 from reecebrowne/bug/2490/2488/image-to-pdf
Img to pdf bug fixes
2024-12-18 10:42:34 +00:00
reecebrowne
a72615cc86 Merge branch 'main' into bug/2490/2488/image-to-pdf 2024-12-18 10:40:54 +00:00
Anthony Stirling
b0daac2566 Merge pull request #2492 from omar-ahmed42/fix-no-formdata-sent-in-multi
Fix: form data not being sent in multi
2024-12-18 10:37:31 +00:00
github-actions[bot]
2335ecf017 📝 Sync README
> Made via sync_files.yml
2024-12-18 10:34:08 +00:00
Anthony Stirling
a79318bf0c Merge pull request #2491 from Ludy87/database_create_m
Adds manual backup creation
2024-12-18 10:33:50 +00:00
Reece Browne
9eed761346 Correct default fit 2024-12-18 00:36:04 +00:00
Reece Browne
12d86049f6 Add default to convert image to pdf api 2024-12-18 00:30:06 +00:00
Reece Browne
e9f80d03ea Fix missing info in form data 2024-12-18 00:16:01 +00:00
Omar Ahmed Hassan
42c238d87d Pass form to submitMultiPdfForm
- Pass form to submitMultiPdfForm to use in FormData creation.
2024-12-18 01:28:04 +02:00
Ludy87
051cd2d0d5 Adds manual backup creation 2024-12-17 23:23:16 +01:00
Anthony Stirling
eb1301464d Update build.gradle 2024-12-17 13:59:30 +00:00
Anthony Stirling
720705e422 Merge pull request #2466 from reecebrowne/bug/fix-multitool-drag
Fix drag and drop bugs and clean up UI
2024-12-17 13:53:17 +00:00
Anthony Stirling
db3a8a8483 Merge branch 'main' into bug/fix-multitool-drag 2024-12-17 13:51:51 +00:00
Reece Browne
2bdda9acba Fix drop on self bug
Fix highlight fadeout bug
UI tweaks
2024-12-17 13:47:44 +00:00
Anthony Stirling
8053035158 Merge pull request #2485 from omar-ahmed42/fix-2478-draggableElement-null
Fix draggableElement is null by removing draggable.js
2024-12-17 11:16:22 +00:00
Anthony Stirling
bd20d3ad93 Merge branch 'main' into fix-2478-draggableElement-null 2024-12-17 11:14:59 +00:00
Anthony Stirling
c6700b34b0 Merge pull request #2484 from Stirling-Tools/sync_readme
📝 Update README: Translation Progress Table
2024-12-17 10:51:11 +00:00
github-actions[bot]
25b66a0775 📝 Sync README
> Made via sync_files.yml
2024-12-17 10:48:55 +00:00
Anthony Stirling
ac9f4a4c7e Merge pull request #2483 from Ludy87/german__17_12
Update messages_de_DE.properties
2024-12-17 10:48:38 +00:00
Ludy
3a0d89bd7c Merge branch 'main' into german__17_12 2024-12-17 11:47:44 +01:00
Omar Ahmed Hassan
5d4769df56 Remove unused draggable.js file
- Remove draggable.js as the draggability feature already works without and it wasn't used anywhere except in sign.html (which throw an exception already as draggableElement was null, and yet the functionality was working as expected, thus we don't need it)
2024-12-17 12:47:13 +02:00
Ludy87
80b9d2691a Update messages_de_DE.properties 2024-12-17 11:46:50 +01:00
Anthony Stirling
99c598096e Merge pull request #2482 from Stirling-Tools/update-3rd-party-licenses
Update 3rd Party Licenses
2024-12-17 10:46:06 +00:00
Anthony Stirling
b3dfdba9cb Merge pull request #2476 from omar-ahmed42/fix-img-to-pdf-2456
Fix img to pdf merge conversion type
2024-12-17 10:36:38 +00:00
Anthony Stirling
c2e96c5f1d Merge branch 'main' into fix-img-to-pdf-2456 2024-12-17 10:35:22 +00:00
GitHub Action
cffdbc5e7b Update 3rd Party Licenses
Signed-off-by: GitHub Action <action@github.com>
2024-12-17 10:28:26 +00:00
Anthony Stirling
e8d7b4aded Merge pull request #2472 from Stirling-Tools/dependabot/gradle/org.springframework-spring-webmvc-6.2.1
Bump org.springframework:spring-webmvc from 6.2.0 to 6.2.1
2024-12-17 10:27:49 +00:00
dependabot[bot]
d672ad22bc Bump org.springframework:spring-webmvc from 6.2.0 to 6.2.1
Bumps [org.springframework:spring-webmvc](https://github.com/spring-projects/spring-framework) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.0...v6.2.1)

---
updated-dependencies:
- dependency-name: org.springframework:spring-webmvc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 10:26:24 +00:00
Anthony Stirling
2c0a9ae082 Merge pull request #2469 from Stirling-Tools/dependabot/gradle/io.micrometer-micrometer-core-1.14.2
Bump io.micrometer:micrometer-core from 1.14.1 to 1.14.2
2024-12-17 10:25:35 +00:00
dependabot[bot]
bbedf22678 Bump io.micrometer:micrometer-core from 1.14.1 to 1.14.2
Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.14.1 to 1.14.2.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.14.1...v1.14.2)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 10:24:19 +00:00
Anthony Stirling
e9431ba6b5 Merge pull request #2474 from Stirling-Tools/dependabot/gradle/org.springframework.security-spring-security-saml2-service-provider-6.4.2
Bump org.springframework.security:spring-security-saml2-service-provider from 6.4.1 to 6.4.2
2024-12-17 10:23:37 +00:00
dependabot[bot]
d74c521bc0 Bump org.springframework.security:spring-security-saml2-service-provider
Bumps [org.springframework.security:spring-security-saml2-service-provider](https://github.com/spring-projects/spring-security) from 6.4.1 to 6.4.2.
- [Release notes](https://github.com/spring-projects/spring-security/releases)
- [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc)
- [Commits](https://github.com/spring-projects/spring-security/compare/6.4.1...6.4.2)

---
updated-dependencies:
- dependency-name: org.springframework.security:spring-security-saml2-service-provider
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 10:22:25 +00:00
Anthony Stirling
fa57ee4ef9 Merge pull request #2471 from Stirling-Tools/dependabot/gradle/io.github.pixee-java-security-toolkit-1.2.1
Bump io.github.pixee:java-security-toolkit from 1.2.0 to 1.2.1
2024-12-17 10:21:39 +00:00
dependabot[bot]
be3cbcc7cd Bump io.github.pixee:java-security-toolkit from 1.2.0 to 1.2.1
Bumps [io.github.pixee:java-security-toolkit](https://github.com/pixee/java-security-toolkit) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/pixee/java-security-toolkit/releases)
- [Commits](https://github.com/pixee/java-security-toolkit/compare/v1.2.0...1.2.1)

---
updated-dependencies:
- dependency-name: io.github.pixee:java-security-toolkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 10:20:25 +00:00
Anthony Stirling
317d985166 Merge pull request #2481 from Ludy87/bump_googleJavaFormat_1_25_2
Bump googleJavaFormat from 1.22.0 to 1.25.2
2024-12-17 10:19:37 +00:00
Anthony Stirling
64f8348215 Merge branch 'main' into bump_googleJavaFormat_1_25_2 2024-12-17 10:18:20 +00:00
Anthony Stirling
7f045dbcc7 Merge pull request #2480 from Ludy87/logger
Remove Direct Logger and Use Lombok `@Slf4j`
2024-12-17 10:17:51 +00:00
Ludy87
343e38a5fd Bump googleJavaFormat from 1.22.0 to 1.25.2 2024-12-17 11:01:17 +01:00
Ludy87
af100d4190 Remove Direct Logger and Use Lombok @Slf4j 2024-12-17 10:26:18 +01:00
Omar Ahmed Hassan
4998ad064a Fix form data files being replaced by the last decrypted file 2024-12-17 01:01:15 +02:00
Omar Ahmed Hassan
64ee26facf Use file-input-change event to disable/enable conversionType 2024-12-17 01:00:21 +02:00
Anthony Stirling
e273b9a3ad Merge pull request #2468 from omar-ahmed42/fix-langs-env-override
Fix: Rename LANGS variable in init.sh to avoid clashing with LANGS env
2024-12-16 22:41:13 +00:00
Omar Ahmed Hassan
42541a7174 Rename LANGS variable in init.sh to avoid overriding font LANGS
Rename LANGS variable in init.sh for TESSERACT_LANGS languages as it conflicts with the variable LANGS that is responsible for installing fonts
2024-12-16 23:55:03 +02:00
Anthony Stirling
ca3002f925 Merge branch 'main' into bug/fix-multitool-drag 2024-12-16 21:21:10 +00:00
Anthony Stirling
6c172af3c1 Merge pull request #2467 from Stirling-Tools/Frooodle-patch-2
update python version used in pre commits
2024-12-16 21:21:01 +00:00
Anthony Stirling
60ced19f64 Update build.yml 2024-12-16 20:59:47 +00:00
Reece Browne
88079ddf7f Reduce opacity of dragged page on multidrag not just single 2024-12-16 19:37:15 +00:00
Reece Browne
9b6dcdcd06 Fix drag and drop bugs and clean up UI 2024-12-16 19:07:15 +00:00
Anthony Stirling
63eb94c0a6 Merge pull request #2463 from reecebrowne/bug/fix_merge
Add missing pdflib
2024-12-16 14:11:36 +00:00
Reece Browne
0a6b6453b2 Update build number 2024-12-16 14:03:38 +00:00
Reece Browne
2931e348a9 Add missing pdflib 2024-12-16 13:55:44 +00:00
Anthony Stirling
c71ca21532 Update multiOSReleases.yml 2024-12-14 11:08:35 +00:00
Anthony Stirling
0e4c3d5dbc Update releaseArtifacts.yml 2024-12-14 11:01:49 +00:00
Anthony Stirling
92cabf125e Merge pull request #2453 from Stirling-Tools/csrf2
Csrf fixes
2024-12-14 10:59:50 +00:00
Anthony Stirling
818bed3154 Update build.gradle 2024-12-14 10:43:35 +00:00
a
faf3454a02 Merge remote-tracking branch 'origin/main' into csrf2 2024-12-14 10:42:26 +00:00
Anthony Stirling
1f1c414138 csrf fixes 2024-12-14 10:42:07 +00:00
Anthony Stirling
f1c5384a37 Merge pull request #2445 from Stirling-Tools/sync_readme
📝 Update README: Translation Progress Table
2024-12-13 20:33:21 +00:00
Anthony Stirling
5607f7079e Merge pull request #2441 from reecebrowne/bug/csrf-decryption-API
CSRF token for decryption
2024-12-13 20:33:03 +00:00
Anthony Stirling
3b8723975d Merge branch 'main' into bug/csrf-decryption-API 2024-12-13 20:31:49 +00:00
Anthony Stirling
41a3d28c90 Merge pull request #2439 from Stirling-Tools/update-3rd-party-licenses
Update 3rd Party Licenses
2024-12-13 20:31:23 +00:00
GitHub Action
f7afe73cb4 Update 3rd Party Licenses
Signed-off-by: GitHub Action <action@github.com>
2024-12-13 20:26:32 +00:00
github-actions[bot]
73e5246191 📝 Sync README
> Made via sync_files.yml
2024-12-13 20:25:58 +00:00
Anthony Stirling
1f39481efe Merge branch 'main' into bug/csrf-decryption-API 2024-12-13 20:25:52 +00:00
Anthony Stirling
5ac2260d78 Merge pull request #2451 from Stirling-Tools/testStuff Full local webUI client
Full local webUI client
2024-12-13 20:25:39 +00:00
Anthony Stirling
bae83a281c Update Dockerfile 2024-12-13 20:07:45 +00:00
Anthony Stirling
dd2aae60ad Update Dockerfile-fat 2024-12-13 20:07:30 +00:00
Anthony Stirling
30ee33002d Merge branch 'main' into testStuff 2024-12-13 19:31:53 +00:00
Anthony Stirling
24717dde19 finish 2024-12-13 18:20:54 +00:00
Anthony Stirling
509a305985 logs and cleanup 2024-12-13 16:58:34 +00:00
Anthony Stirling
13572a7f18 remove non windows for now 2024-12-13 16:04:45 +00:00
Anthony Stirling
ebd0ddc6ad test 2024-12-13 12:14:21 +00:00
Anthony Stirling
43c4ec1089 fixes! 2024-12-13 11:31:49 +00:00
Anthony Stirling
1ccdc1697b dif main class 2024-12-13 10:49:04 +00:00
Anthony Stirling
2297c5dc95 Merge pull request #2442 from lhui/main
add and refactor CN translate
2024-12-13 10:29:13 +00:00
Anthony Stirling
859c9942e6 Merge pull request #2444 from tkymmm/main
Update messages_ja_JP.properties
2024-12-13 10:28:57 +00:00
Anthony Stirling
c723696bd0 Merge pull request #2443 from albanobattistella/patch-61
Update messages_it_IT.properties
2024-12-13 10:28:46 +00:00
Anthony Stirling
fe198458ca fix main class 2024-12-13 10:28:03 +00:00
tkymmm
378aca4460 Update messages_ja_JP.properties 2024-12-13 13:23:26 +09:00
Anthony Stirling
9870e6ad7c fix 2024-12-13 00:59:42 +00:00
Anthony Stirling
40b5904726 test 2024-12-13 00:52:12 +00:00
Anthony Stirling
de3f59cf44 destination = "${projectDir}/build/jpackage" 2024-12-13 00:47:37 +00:00
Anthony Stirling
4ae7c83357 tests 2024-12-13 00:44:32 +00:00
Anthony Stirling
f127271709 test 2024-12-13 00:31:39 +00:00
Anthony Stirling
f899088c75 test 2024-12-13 00:29:03 +00:00
Anthony Stirling
d55e007f93 test dirs 2024-12-13 00:24:51 +00:00
Anthony Stirling
ccdcb05e65 x test 2024-12-13 00:13:47 +00:00
Anthony Stirling
e6c2ad8c9c test 2024-12-13 00:00:56 +00:00
Anthony Stirling
32aa623c8b test 2024-12-12 23:57:12 +00:00
Anthony Stirling
23888c5d2c test 2024-12-12 23:50:51 +00:00
Anthony Stirling
2f23eb69c6 naming 2024-12-12 23:40:49 +00:00
Anthony Stirling
446cedf26e unix name 2024-12-12 23:26:23 +00:00
Anthony Stirling
f950e25dad versioning 2024-12-12 23:22:19 +00:00
Anthony Stirling
65a86a6e97 remove mac 18+ 2024-12-12 23:16:27 +00:00
Anthony Stirling
7f4134f52d info 2024-12-12 23:08:28 +00:00
Anthony Stirling
55969697b8 build 2024-12-12 23:05:59 +00:00
Anthony Stirling
86662d9cf6 more testing 2024-12-12 23:03:42 +00:00
albanobattistella
2437460c73 Update messages_it_IT.properties 2024-12-12 19:00:26 +01:00
lihui
a7960d992c add and refactor CN translate 2024-12-13 00:42:18 +08:00
Reece Browne
c98cd8117f CSRF token for decryption 2024-12-12 13:07:50 +00:00
Anthony Stirling
50c5efac87 Merge pull request #2440 from MaratheHarshad/fix/collapsible-menus-on-reload
Fix collapsed menu issue on page reload
2024-12-12 11:48:54 +00:00
Anthony Stirling
0952245b23 Merge pull request #2425 from Stirling-Tools/dependabot/docker/alpine-3.21.0
Bump alpine from 3.20.3 to 3.21.0
2024-12-12 11:32:16 +00:00
dependabot[bot]
83ddfdf152 Bump alpine from 3.20.3 to 3.21.0
Bumps alpine from 3.20.3 to 3.21.0.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-12 11:29:57 +00:00
Anthony Stirling
08777c78b1 Merge pull request #2423 from Stirling-Tools/dependabot/gradle/org.thymeleaf.extras-thymeleaf-extras-springsecurity5-3.1.3.RELEASE
Bump org.thymeleaf.extras:thymeleaf-extras-springsecurity5 from 3.1.2.RELEASE to 3.1.3.RELEASE
2024-12-12 11:29:22 +00:00
Anthony Stirling
c6980e9693 Merge pull request #2434 from Ludy87/security_fix_1
Security fix: Server-Side Request Forgery
2024-12-12 11:28:56 +00:00
Anthony Stirling
fc514ee65b Merge pull request #2436 from Stirling-Tools/snyk-fix-9e2683c5d747da5ff97f3f913479b288
[Snyk] Security upgrade alpine from 3.20.3 to 3.21.0
2024-12-12 11:28:37 +00:00
Harshad Marathe
44be2b99d2 Fix collapsed menu after reload on state closed 2024-12-12 16:55:40 +05:30
Anthony Stirling
549824c91f Merge pull request #2438 from Stirling-Tools/sync_readme
📝 Update README: Translation Progress Table
2024-12-12 11:08:31 +00:00
github-actions[bot]
1c0d1efda6 📝 Sync README
> Made via sync_files.yml
2024-12-12 11:01:38 +00:00
Anthony Stirling
9d8d90bf2f Merge pull request #2432 from Stirling-Tools/update_translation_files
Update translation files
2024-12-12 11:01:22 +00:00
snyk-bot
a7e7d57b23 fix: Dockerfile-ultra-lite to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE320-OPENSSL-8235201
- https://snyk.io/vuln/SNYK-ALPINE320-OPENSSL-8235201
2024-12-12 00:48:00 +00:00
Anthony Stirling
e014bb023b fixes 2024-12-11 23:21:56 +00:00
Anthony Stirling
1c5dfc46a0 fixes 2024-12-11 23:13:23 +00:00
Anthony Stirling
11273b1589 fix gha release 2024-12-11 22:29:58 +00:00
Anthony Stirling
dd5af46906 gha fix 2024-12-11 22:17:27 +00:00
Anthony Stirling
c20d37518d prop fixes 2024-12-11 22:09:35 +00:00
Anthony Stirling
eb20f51958 headless 2024-12-11 21:56:50 +00:00
Anthony Stirling
97d28ac6d2 Windows UI .exe 2024-12-11 21:54:05 +00:00
Anthony Stirling
026fe8150d Merge pull request #2427 from Stirling-Tools/testStuff
X-API-key to X-API-KEY and enable CSRF protection for all users
2024-12-11 21:52:57 +00:00
Ludy87
c3f88f716c Update GeneralUtils.java 2024-12-11 21:10:18 +01:00
Ludy87
67f983f00d Security fix: Server-Side Request Forgery
https://github.com/Stirling-Tools/Stirling-PDF/security/advisories/GHSA-4v4c-9hpr-93vx
2024-12-11 21:06:07 +01:00
github-actions[bot]
9167f12296 Update translation files
Signed-off-by: GitHub Action <action@github.com>
2024-12-11 17:28:28 +00:00
Anthony Stirling
93e190fdeb Merge pull request #2412 from reecebrowne/feature/1856/decrypt
Feature/1856/decrypt
2024-12-11 17:27:28 +00:00
Anthony Stirling
82bebf5c62 Merge branch 'main' into feature/1856/decrypt 2024-12-11 17:26:14 +00:00
Reece Browne
bb3f076e6d Final pages fixed for decryption 2024-12-11 16:55:27 +00:00
Reece Browne
64dfa4b841 Tweak additional files to integrate decryption and clean up js 2024-12-10 22:21:00 +00:00
thiagoor-cpu
0f6f3f305a Update messages_pt_BR.properties (#2429)
Up-to-date translation PT-BR
2024-12-10 20:40:28 +00:00
Anthony Stirling
58c7d7b9a8 X-API-key to X-API-KEY 2024-12-10 20:39:24 +00:00
Reece Browne
ef8231de3a Add Decrypt to all relevant pages 2024-12-10 16:39:06 +00:00
Anthony Stirling
c1c3eba398 ensure csrf is enabled 2024-12-10 11:17:50 +00:00
dependabot[bot]
52693541d9 Bump org.thymeleaf.extras:thymeleaf-extras-springsecurity5
Bumps org.thymeleaf.extras:thymeleaf-extras-springsecurity5 from 3.1.2.RELEASE to 3.1.3.RELEASE.

---
updated-dependencies:
- dependency-name: org.thymeleaf.extras:thymeleaf-extras-springsecurity5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-09 22:44:23 +00:00
Anthony Stirling
1639e0fc4c format 2024-12-09 20:41:13 +00:00
Anthony Stirling
0652299bec fixes 2024-12-09 20:40:59 +00:00
Reece Browne
1d6511b043 Check if file is encrypted without password 2024-12-09 13:20:08 +00:00
F43Z
a400fe6015 feat: fa locale added (#2416)
* feat: fa locale added

* fix: fa_locale issues fixed

* Update settings.json
2024-12-08 20:19:52 +00:00
Sai Kumar
b47df3d252 Text color selection for watermark (#2415)
* added custom color selection for Watermark

* using the same translation as AddStampRequest.customColor for the new watermark.customColor

* fixed the space issue between words
2024-12-07 14:19:50 +00:00
Michael Bohn
cb6e1cd94e Update messages_de_DE.properties (#2410)
Some minor changes/fixes to german translation.
2024-12-07 12:15:52 +00:00
Reece Browne
6ee6254f5a Additional decryption translations 2024-12-06 21:26:28 +00:00
Reece Browne
f2c9549ba1 Password prompt translations 2024-12-06 20:53:16 +00:00
Reece Browne
58278c07ff Translations for errors 2024-12-06 20:46:04 +00:00
Reece Browne
4d017610b8 PDF decryption 2024-12-06 19:08:18 +00:00
Reece Browne
dcafc0d487 Merge branch 'decrypt' of https://github.com/Stirling-Tools/Stirling-PDF 2024-12-06 15:33:44 +00:00
github-actions[bot]
2ec8c97737 📝 Update README: Translation Progress Table (#2409)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-06 12:27:14 +00:00
Anthony Stirling
397a07afe8 english translation fixes (#2408)
* english

* Update messages_en_GB.properties
2024-12-06 12:25:08 +00:00
github-actions[bot]
b072c39fd9 📝 Update README: Translation Progress Table (#2406)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-06 10:28:05 +00:00
Ludy
1bc6b4149c Update German translation + remove unnecessary comment (#2405) 2024-12-06 10:23:00 +00:00
Anthony Stirling
5a5a8bb7ba quick fixes (#2404) 2024-12-05 19:55:34 +00:00
github-actions[bot]
b6eca59f23 📝 Update README: Translation Progress Table (#2402)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-05 19:54:34 +00:00
Anthony Stirling
7108424a92 Update build.gradle 2024-12-05 19:16:18 +00:00
albanobattistella
400965ffc8 Update messages_it_IT.properties (#2401) 2024-12-05 17:39:49 +00:00
github-actions[bot]
1895a04394 📝 Update README: Translation Progress Table (#2399)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-05 17:00:41 +00:00
Omar Ahmed Hassan
f8f137a30a Feature: Show permissions as a separate tab (#2396)
Show permissions as a separate tab

- Move permissions code into a separate for better readability and maintainability.
- Separate `Permissions` node from `Encryption` so that it would be displayed in the frontend as a separate tab.
- Use more user friendly permission labels such as replacing `canModify` with `Modifying` and values such as `Allowed` and `Not Allowed` instead of `true`, `false`.
- Show permissions regardless of the encryption state.
2024-12-05 17:00:23 +00:00
github-actions[bot]
f6a2d4784b Update translation files (#2398)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-05 16:02:35 +00:00
reecebrowne
526dc9f911 Only download one file on sign cert (#2397) 2024-12-05 15:58:27 +00:00
Anthony Stirling
cce9f74eb9 PDF Cert validation (#2394)
* verifyCerts

* cert info

* Hardening suggestions for Stirling-PDF / certValidate (#2395)

* Protect `readLine()` against DoS

* Switch order of literals to prevent NullPointerException

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>

* some basic html excaping and translation fixing

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
Co-authored-by: a <a>
2024-12-05 15:56:22 +00:00
Omar Ahmed Hassan
0e3865618d Fix missing upload button (#2393)
The code snippet `input[type=file]{ display: none;}` was unintentionally hiding the upload button, to fix this, it was changed to only target input within `.input-container`
2024-12-05 11:16:16 +00:00
reecebrowne
d888ed1ae0 Feature/undo page break (#2389)
* Fix delete selected
Fix add page break where selected
Added undo logic for page breaks

* Add pages undo capability

* Fix page break when selected logic
2024-12-05 10:43:31 +00:00
Anthony Stirling
99d1b46d97 Update MetricsAggregatorService.java 2024-12-03 15:26:40 +00:00
Anthony Stirling
32e46eeb73 Update build.gradle 2024-12-03 10:54:07 +00:00
Omar Ahmed Hassan
b7da84d257 Fix deserialization failure in Change Metadata (#2382)
* Fix deserialization failure from String to Map

Fix deserialization failure from String to Map that caused the following exception:
Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<byte[]> stirling.software.SPDF.controller.api.misc.MetadataController.metadata(stirling.software.SPDF.model.api.misc.MetadataRequest) throws java.io.IOException: [Field error in object 'metadataRequest' on field 'allRequestParams': rejected value [{"customKey1" : "YourCustomKey", "customKeyValue1", "YourCustomValue"}]; codes [typeMismatch.metadataRequest.allRequestParams,typeMismatch.allRequestParams,typeMismatch.java.util.Map,typeMismatch];

* Fix form binding for dynamic Map entries in Change Metadata

- Implemented support for dynamic key-value inputs in Change Metadata form using proper `name` attributes for Map (`allRequestParams`) binding.
- Fix form binding for dynamic Map (`allRequestParams`) entries in Change Metadata as the `allRequestParams` (Map name) was being sent as an empty map.
2024-12-03 08:28:34 +00:00
github-actions[bot]
1c1ead5d62 📝 Update README: Translation Progress Table (#2381)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-02 19:25:16 +00:00
albanobattistella
6ff53aa5b3 Update messages_it_IT.properties (#2380) 2024-12-02 18:59:05 +00:00
github-actions[bot]
8d60b08cd9 📝 Update README: Translation Progress Table (#2379)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-02 18:32:07 +00:00
github-actions[bot]
64cf5167c0 Update translation files (#2378)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-02 18:18:16 +00:00
Omar Ahmed Hassan
de4637e8d4 Fix drag and drop area for file choosers by adding separate ones (#2368)
* Add separate drag and drop area for file choosers

 - Add separate drag and drop area for file choosers

### Why?
Previously, when there were multiple file choosers in the same page, if you attempted to drag and drop any files, they would be added to both file choosers as it was designed at first to handle 1 file chooser present, now that we have multiple ones, it is necessary to adapt our design to match the changing functionality.

### Can you not preserve the old overlay when there's only one file chooser present?
Yes, we can, but imagine as a user, you try to drag and drop a file in one page and the fields turn into drag and drop areas then you go to another page and try to drag and drop again but you encounter the old overlay instead, as a user you might get confused and ask yourself "What changed?" or if a user is telling another user the steps to drag and drop files and he didn't know about this case, then it would still be confusing, thus consistency is preferred in this case.

* Update file chooser UI

* Add support for listing and removing selected files and their file icons

- Selected files are listed below the file chooser in a selected files container.
- Users can now remove uploaded/selected files.
- Hide selected files container/box unless there are files selected/uploaded.
- Add separate overlay for each drag & drop area.

## FAQ:
- Why did you assign a unique id to each file? isn't the filename enough?
= Because a user might upload multiple files with the same name, if the user wanted to remove one of them, how would we differentiate between them? we won't be able to unless we assign an identifier, you might argue "we remove based on the filename and size", then what if the user uploaded the same file more than once (intentionally), then we would accidentally remove all the files even though that is not what the user wanted, so going with unique ID approach would prevent this issue/problem from occurring in the first place.

* Rename remove-file css class to remove-selected-file

- Rename remove-file css class to remove-selected-file to avoid css conflict with remove-file in merge.css

* Use input element to dispatch event on file removal

Use the correct element to dispatch "file-input-change" (input element is the correct one).

* Adapt file chooser UI to themes

- Adapt file chooser UI to themes by adjusting their font colors and background colors.
- Make text more visible in overlay by increasing the font size by 0.1rem and setting font weight to 550.

* Remove extra overlay border

- Removing overlay's border as it is unnecessary and only causing a double border issue on the file input container.

* Remove Browse button, highlight file chooser and make it clickable

- Remove browse button.
- Make the entire file chooser container clickable.
- Add glowing effect on hover for file chooser.
- Change color of file chooser on hover.

* Replace crypto.randomUUID() with UUID.uuidv4()

- Replace crypto.randomUUID() with UUID.uuidv4() as crypto.randomUUID() is only supported in secured contexts such as localhost 127.0.0.1 and over HTTPS

* Fix merge file removal not being reflected in file chooser

- Files removed from the list in merge page would now be reflected in the file chooser's container.

* Make inputElement optional in removeFileById

- Make inputElement optional in removeFileById, this way we could control changing inputElements files.

* Add translation support to file chooser

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-12-02 18:10:12 +00:00
Sai Kumar
3c0a8071dc added support for new line break in stampController (#2370)
added support for new line in stampController

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-12-02 17:48:19 +00:00
Omar Ahmed Hassan
04ccdf6f76 Fix: prevent fileInput.js from adding event listeners more than once (#2365)
Fix fileInput.js adding event listeners more than once

- Fix a bug that caused fileInput.js to add event listeners more than once per HTML file as it's included in fileSelector fragment in fragments/common.html thus it's being loaded N times where N is the number of file selectors / custom file chooser / file input elements per HTML file, which resulted in each event actions being executed N times as well, which was prevalent in drag and drop operations such as dragging and dropping a file called y.png, it would be duplicated N times (as in /sign path).
2024-12-02 17:41:11 +00:00
Omar Ahmed Hassan
db02fba31f Fix translations for watermark spacers (#2369)
Fix translations by adding a space between width/height and spacer and capitalize the first letter
2024-12-02 17:01:19 +00:00
Omar Ahmed Hassan
5b6f649e4e Fix submit button in crop by adding id (#2374)
- Add missing ID to submit button in crop page.
2024-12-02 10:40:46 +00:00
Omar Ahmed Hassan
de23bb702c Fix allowing multiple files to be dropped onto a single file input (#2359)
Fix a bug that allowed multiple files to be dropped onto a single-file input element

- Fix a bug that allowed multiple files to be dropped onto a single-file input element by accepting only the first file.
2024-11-29 17:31:14 +00:00
github-actions[bot]
25e564154e Update 3rd Party Licenses (#2362)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-29 15:41:29 +00:00
Anthony Stirling
3633a979d3 fixes and other changes and debug of WIP SAML (#2360)
* backup

* remove debugs

* oauth to saml and compare fixes etc

* ee flag for saml

* more fixes

* info to debug

* remove unused repo

* spring dev fix for saml

* debugs

* saml stuff

* debugs

* fix
2024-11-29 15:11:59 +00:00
Omar Ahmed Hassan
99d481d69f Fix Array.from syntax in nonmultiple file upload (#2357)
- Fix Array.from syntax in nonmultiple file upload as Array.from(<non-array or string>) returns an empty array which is the case when a file is selected from an input element (when multiple attribute isn't  supported) which can be found in Array.from(element.files[0]) -> results in an empty array.
2024-11-29 12:22:52 +00:00
github-actions[bot]
a5ba6c403a 📝 Update README: Translation Progress Table (#2356)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-28 15:39:39 +00:00
albanobattistella
b2e6d89d16 Update messages_it_IT.properties (#2355) 2024-11-28 14:42:55 +00:00
github-actions[bot]
b59d2d15b4 Update translation files (#2354)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-28 14:42:29 +00:00
Omar Ahmed Hassan
61e750646c Feature: Undo Redo options multi tool #2297 (#2348)
* Implement Command class for Command Pattern

Created a base `Command` class to implement the **Command Pattern**. This class provides a skeletal implementation for `execute`, `undo`, and `redo` methods.

**Note:** This class is intended to be subclassed and not instantiated directly.

* Add undo/redo stacks and operations

* Use rotate element command to perform execute/undo/redo operations

* Handle commands executed through events
- Add "command-execution" event listener to execute commands that are not invoked from the same class while adding the command to the undo stack and clearing the redo stack.

* Add and use rotate all command to rotate/redo/undo all elements

* Use command pattern to delete pages

* Use command pattern for page selection

* Use command pattern to move pages up and down

* Use command pattern to remove selected pages

* Use command pattern to perform the splitting operation

* Add undo/redo functionality with filename input exclusion

- Implement undo (Ctrl+Z) and redo (Ctrl+Y) functionality.
- Prevent undo/redo actions when the filename input field is focused.
- Ensures proper handling of undo/redo actions without interfering with text editing.

* Introduce UndoManager for managing undo/redo operations

 - Encapsulate undo/redo stacks and operations within UndoManager.
- Simplify handling of undo/redo functionality through a dedicated manager.

* Call execute on splitAllCommand

- Fix a bug that caused split all functionality to not work as execute() wasn't called on splitAllCommand

* Add undo/redo buttons to multi tool

- Add undo/redo buttons to multi tool
- Dispatch an event upon state change (such as changes in the undo/redo stacks) to update the UI accordingly.

* Add undo/redo to translations

* Replace hard-coded "Undo"/"Redo" with translation keys in multi tool

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-28 14:25:13 +00:00
Omar Ahmed Hassan
de9c21b3de Fix: page break insertion functionality in Multi Tool (#2350)
Fix page break insertion functionality

- Page Break insertion functionality now successfully inserts page breaks upon request
2024-11-28 10:21:14 +00:00
Nureddin Farzaliyev
b32d6cb858 Azerbaijani Language Translation (#2347)
* Azerbaijani flag and dropdown item added

* Azerbaijani Language file Added

* AZ - ignore_translation.toml init

* AZ Translation Enterprise Edition Section

* Translation for Generic

* translation-az pipeline

* Translation for Analytics

* Translation for NAVBAR

* Translation for SETTINGS

* translation-az homepage

* Translation for #login

* Translation for (showJS)

* Translation for #showJS

* Translation for #PDFToWord

* Translation for #PDFToPresentation

* Translation for #PDFToText

* Translation for #PDFToHTML

* Translation for #PDFToXML

* Translation for #PDFToCSV

* Translation for #repair

* Translation for #pageLayout

* Translation for #pdfToSinglePage

* Translation for #pageExtracter

* Translation for #getPdfInfo

* Translation for #markdown-to-pdf

* Translation for #PDFToXML

* Translation for #html-to-pdf

* Translation for #PDFToHTML

* Translation for #PDFToText

* Translation for #PDFToPresentation

* Translation for #PDFToWord

* Translation for #PDFToCSV

* Translation for #url-to-pdf

* Translation for #pdfToImage

* Translation for #BookToPDF

* Translation for #PDFToBook

* Translation for #autoRedact

* Translation for #Add image

* Translation for #File to PDF

* Translation for (remove-image)

* Translation for (remove-image)

* Translation for (survey)

* Translation for (licenses)

* Translation for (printFile)

* Translation for (split-bysections)

* Translation for (overlay-pdfs)

* Translation for (split-by-size-or-count)

* Translation for (addPageNumbers)

* Translation for (adjustContrast)

* Translation for (autoSplitPDF)

* Translation for (scalePages)

* Translation for (removeCertSign)

* Translation for (removeAnnotations)

* Translation for (sign)

* Translation for (flatten)

* Translation for (extractImages)

* Translation for (merge)

* Translation for (view pdf)

* Translation for (pageRemover)

* Translation for (rotate)

* az Translation for replace-invert-color

* az Translation for addstamprequest

* Translation for (remove-image)

* Translation for #url-to-pdf

* Update messages_az_AZ.properties

* Update README.md

* Update README.md

* Update messages_az_AZ.properties

* Translation for #compress

* Translation for #Change permissions

* Translation for #remove password

* Translation for #pdfToPDFA

* Translation for #changeMetadata

* Translation for #merge

* Translation for #split-pdfs

* translation-az addpass

* translation-az watermark

* translation-az removeblanks

* translation-az compare

* translation-az certsign

* Translation for #pdfOrganiser

* Translation for #multiTool

* Translation for #multiTool

* az translation scannerimagesplit

* az translation ocr

* az translation fix

* az translation linebreak fix

* az translation linebreak fix

---------

Co-authored-by: Lucifer25x <lucifer25x@protonmail.com>
Co-authored-by: islamd7 <vusal04999@gmail.com>
Co-authored-by: Valida Rahmanova <validerehmanova04@gmail.com>
Co-authored-by: yusif043-bit <yusif.abbaszade.043@gmail.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-27 18:09:42 +00:00
pixeebot[bot]
d832a90de0 (CodeQL) Fixed finding: "Arbitrary file access during archive extraction ("Zip Slip")
" (#2344)

(CodeQL) Fixed finding: "Arbitrary file access during archive extraction ("Zip Slip")
"

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2024-11-27 07:16:03 +00:00
Anthony Stirling
212e521238 Update MetricsAggregatorService.java 2024-11-26 21:30:47 +00:00
github-actions[bot]
0915e72a3d 📝 Update README: Translation Progress Table (#2341)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-26 20:52:54 +00:00
github-actions[bot]
ee5013651f Update 3rd Party Licenses (#2342)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-26 20:52:39 +00:00
github-actions[bot]
4aa44e6fc0 Update translation files (#2343)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-26 20:51:57 +00:00
github-actions[bot]
41c743a9f8 Update 3rd Party Licenses (#2337)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-26 20:51:02 +00:00
Anthony Stirling
833b3c45c6 Removal of Ghostscript to use qpdf and tesseract directly (#2338)
* navbar fix multi tool and compress location

* release notes and ghostscript removal

* cleanups

* formatting

* update docs

* more

* more

* docs

* release bump

* Hardening suggestions for Stirling-PDF / ghostscript (#2339)

* Protect `readLine()` against DoS

* Sanitized user-provided file names in HTTP multipart uploads

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>

---------

Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com>
2024-11-26 20:50:35 +00:00
Omar Ahmed Hassan
654bc94d44 Fix: input file overwrite in merge (#2335)
* Fix input files being overwritten by newly uploaded files

- Fix a bug that caused existing selected/uploaded files to be overwritten when a new input file is uploaded through input element.
- Add source property to change event to differentiate between uploaded files using input element and drag/drop uploads to avoid processing drag/drop files more than once, thus avoiding file duplication (file duplication resulting from copying drop/drop files to input files on each 'change' event).

* Dispatch and use file-input-change instead of change event for merging

- Dispatch "file-input-change" event after each "change" event in file upload, to notify other functions/components relying on the files provided by the \<input\> element.
- Use "file-input-change" instead of "change" event to display the latest version of uploaded files.

# FAQ:
- Why use "file-input-change" instead of "change" in merge.js?
= "change" event is automatically triggered when a file is uploaded through \<input\> element which would replace all the existing selected/uploaded files including the drag/drop files.

## Example:
Let's say that the user wants to upload/select the x.pdf, y.pdf and z.pdf all together:

- user selects "x.pdf" -> file selected successfully.
= selected files: x.pdf

- user drags and drops "y.pdf" -> file dropped successfully
= selected files: x.pdf, y.pdf

- user selects again using \<input\> "z.pdf" -> file selected succesfully overwriting selected files.
= selected files: z.pdf
2024-11-26 20:41:08 +00:00
dependabot[bot]
86fa404c90 Bump commons-io:commons-io from 2.17.0 to 2.18.0 (#2333)
Bumps commons-io:commons-io from 2.17.0 to 2.18.0.

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-26 20:38:51 +00:00
Anthony Stirling
1db1370420 Update README.md 2024-11-26 10:26:44 +00:00
github-actions[bot]
ee4b7e02ab 📝 Update README: Translation Progress Table (#2327)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-26 08:17:56 +00:00
albanobattistella
e6c5634165 Update messages_it_IT.properties (#2334) 2024-11-26 08:17:09 +00:00
Anthony Stirling
5188eb3b04 Update build.gradle 2024-11-26 08:16:45 +00:00
Anthony Stirling
3fa6bcb2ee navbar fix multi tool and compress location (#2331) 2024-11-25 21:42:49 +00:00
github-actions[bot]
0b359ad4a8 Update translation files (#2329)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-25 20:55:56 +00:00
reecebrowne
23ee77f6ab Additional sign tooltips (#2328)
* Add tooltip to sign add to all pages feature

* Additional Tooltips
2024-11-25 20:43:05 +00:00
github-actions[bot]
bfc1ed2b39 Update translation files (#2326)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-25 20:13:03 +00:00
reecebrowne
da46d942ba Add tooltip to sign add to all pages feature (#2325) 2024-11-25 20:11:27 +00:00
Anthony Stirling
5936e856f0 metrics 2024-11-25 14:02:17 +00:00
github-actions[bot]
fd906d36dd Update 3rd Party Licenses (#2321)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-24 17:21:54 +00:00
Ludy
8f4709d82e Bump com.h2database:h2 from 2.1.214 to 2.3.232 (#2314) 2024-11-24 14:36:53 +00:00
dependabot[bot]
8445f2719b Bump org.springframework:spring-webmvc from 6.1.14 to 6.2.0 (#2268)
Bumps [org.springframework:spring-webmvc](https://github.com/spring-projects/spring-framework) from 6.1.14 to 6.2.0.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.14...v6.2.0)

---
updated-dependencies:
- dependency-name: org.springframework:spring-webmvc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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>
2024-11-24 14:36:30 +00:00
github-actions[bot]
eaa64e1471 Update 3rd Party Licenses (#2318)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-24 12:16:01 +00:00
Ludy
4abb0cb85e Fix: id for submit button added (#2320) 2024-11-24 10:31:50 +00:00
Omar Ahmed Hassan
afad06bed4 Extract tables from PDF to CSV using Tabula (#2312)
* Add Tabula dependency and exclude slf4j-simple

- Add tabula-java dependency to extract tables into CSV.
- Exclude slf4j-simple due to Logback

* Add a flexible CSVWriter

- Add FlexibleCSVWriter which extends CSVWriter to pass a custom CSVFormat, as CSVWriter's parameterized constructor (that allows changing CSVFormat) is protected.

* Use Tabula in extracting tables from PDF

- Use Tabula in extracting tables from PDF instead of the existing implementation

* Delete PDFTableStripper as It is unneeded

- Delete PDFTableStripper as It is unneeded as Tabula-Java is used instead.

* Use correct class in ExtractCSVController logger

* Exclude gson and bcprov-jdk15on dependencies from tabula

- Exclude gson and bcprov-jdk15on from tabula-java due to detected security vulnerabilities.
2024-11-23 23:28:44 +00:00
github-actions[bot]
faa8a9752c 📝 Update README: Translation Progress Table (#2317)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-23 23:05:40 +00:00
Omar Ahmed Hassan
989538e340 Update Arabic Language for Multi tool section (#2316)
* Update Arabic Language for Multi tool section

* Add a back line at 955-956
2024-11-23 22:36:21 +00:00
Ludy
5b8bdc3352 improves readability (#2313) 2024-11-23 22:09:46 +00:00
Thomas BERNARD
f559eaa4e8 French translation (again) (#2315)
* French translations for multiTool

* fix french translation invalidPasswordMessage/confirmPasswordErrorMessage

* french translation : reset/navbar.search/sign.saved

* fix my invalidPasswordMessage french translation :)
2024-11-23 22:09:27 +00:00
github-actions[bot]
f306e00fba Update 3rd Party Licenses (#2310)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-23 12:16:20 +00:00
github-actions[bot]
e09d6f9998 📝 Update README: Translation Progress Table (#2311)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-23 12:16:16 +00:00
Ludy
3a27aa16d5 Improves security when processing properties files (#2303)
* Improves security when processing properties files

* Check for spaces in the key

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-23 11:49:49 +00:00
Ludy
9a96109ea2 Fix: Prevents duplicate listing of search results (#2306) 2024-11-23 11:37:13 +00:00
albanobattistella
ad1cce378f Update messages_it_IT.properties (#2307)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-23 10:53:26 +00:00
Ludy
9abb105835 Fix: Fixes dependency bug and replaces obsolete method (#2309) 2024-11-23 10:51:17 +00:00
Renan
204bae3bc1 Sign multiple PDF pages at the same time in the same location (#2008) (#2278)
* Sign multiple PDF pages at the same time in the same location (#2008)

* Modifying the functionality of how the signature is added to all pages (#2008)

* Adding the functionality to reverse the addition on all pages and implementing buttons to navigate to the first and last pages (#2008)
2024-11-22 17:40:09 +00:00
reecebrowne
547f23fe78 Posthog multitool (#2301)
* Posthog functionality

* Posthog in multitool

* check if anylitics enabled
2024-11-22 17:38:44 +00:00
Rafael Encinas
543ad083a2 Fix file clear for errors (#2302)
* Prevent file input from being removed when an error occurs

* Fix a bug preventing fetch when 'Bored waiting' btn isn't present
2024-11-22 17:11:23 +00:00
github-actions[bot]
61bccd1d8b 📝 Update README: Translation Progress Table (#2300)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-22 15:27:11 +00:00
github-actions[bot]
83be709299 Update translation files (#2298)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-22 15:17:04 +00:00
reecebrowne
0e602153f3 Feature/2198/multitool multi select move pages (#2294)
* Multitool - Select multiple pages for rotation tool

* Multitool multi select delete feature

* Multitool multi select UI improvements and big fixes

* Multitool multi select select all and UI improvements

* Multi tool multi select, download selected, clean up and bug fixes

* Groundwork for multiselect drag and drop

* Multi select drag and drop finalised

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* Turn off select mode after multidrag

---------

Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-22 11:39:22 +00:00
Anthony Stirling
597619740a Update build.gradle 2024-11-22 09:29:41 +00:00
github-actions[bot]
41a39a0a94 Update 3rd Party Licenses (#2295)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-22 09:22:03 +00:00
dependabot[bot]
b14fba064d Bump org.springframework.security:spring-security-saml2-service-provider from 6.3.4 to 6.4.1 (#2296)
Bump org.springframework.security:spring-security-saml2-service-provider

Bumps [org.springframework.security:spring-security-saml2-service-provider](https://github.com/spring-projects/spring-security) from 6.3.4 to 6.4.1.
- [Release notes](https://github.com/spring-projects/spring-security/releases)
- [Changelog](https://github.com/spring-projects/spring-security/blob/main/RELEASE.adoc)
- [Commits](https://github.com/spring-projects/spring-security/compare/6.3.4...6.4.1)

---
updated-dependencies:
- dependency-name: org.springframework.security:spring-security-saml2-service-provider
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 09:19:58 +00:00
alonsofabila-dev
bd29dd1ac3 Bored waiting button doesnt remove itself after processing (#2079) (#2235)
Fix: Bored waiting button doesnt remove itself after processing (#2079)

hide bored waiting? button after request handling both success and error cases to properly hide the button.

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-22 09:14:26 +00:00
dependabot[bot]
6d3f14375e Bump bouncycastleVersion from 1.78.1 to 1.79 (#2177)
Bumps `bouncycastleVersion` from 1.78.1 to 1.79.

Updates `org.bouncycastle:bcprov-jdk18on` from 1.78.1 to 1.79
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

Updates `org.bouncycastle:bcpkix-jdk18on` from 1.78.1 to 1.79
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcprov-jdk18on
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bouncycastle:bcpkix-jdk18on
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 09:06:30 +00:00
dependabot[bot]
888aec5701 Bump io.micrometer:micrometer-core from 1.13.6 to 1.14.1 (#2253)
Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.13.6 to 1.14.1.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.13.6...v1.14.1)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 09:06:16 +00:00
dependabot[bot]
92e7e85e77 Bump gradle from 8.7-jdk17 to 8.11-jdk17 (#2269)
* Bump gradle from 8.7-jdk17 to 8.11-jdk17

Bumps gradle from 8.7-jdk17 to 8.11-jdk17.

---
updated-dependencies:
- dependency-name: gradle
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update gradle-wrapper.properties

---------

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>
2024-11-22 09:02:22 +00:00
github-actions[bot]
3bf467e4ff 📝 Update README: Translation Progress Table (#2283)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-22 08:45:22 +00:00
yusif043-bit
dc1887db4d Translation az (#2287)
* Azerbaijani flag and dropdown item added

* Azerbaijani Language file Added

* AZ - ignore_translation.toml init

* AZ Translation Enterprise Edition Section

* Translation for Generic

* translation-az pipeline

* Translation for Analytics

* Translation for NAVBAR

* Translation for SETTINGS

* translation-az homepage

* Translation for #login

* Translation for (showJS)

* Translation for #showJS

* Translation for #PDFToWord

* Translation for #PDFToPresentation

* Translation for #PDFToText

* Translation for #PDFToHTML

* Translation for #PDFToXML

* Translation for #PDFToCSV

* Translation for #repair

* Translation for #pageLayout

* Translation for #pdfToSinglePage

* Translation for #pageExtracter

* Translation for #getPdfInfo

* Translation for #markdown-to-pdf

* Translation for #PDFToXML

* Translation for #html-to-pdf

* Translation for #PDFToHTML

* Translation for #PDFToText

* Translation for #PDFToPresentation

* Translation for #PDFToWord

* Translation for #PDFToCSV

* Translation for #url-to-pdf

* Translation for #pdfToImage

* Translation for #BookToPDF

* Translation for #PDFToBook

* Translation for #autoRedact

* Translation for #Add image

* Translation for #File to PDF

* Translation for (remove-image)

* Translation for (remove-image)

* Translation for (survey)

* Translation for (licenses)

* Translation for (printFile)

* Translation for (split-bysections)

* Translation for (overlay-pdfs)

* Translation for (split-by-size-or-count)

* Translation for (addPageNumbers)

* Translation for (adjustContrast)

* Translation for (autoSplitPDF)

* Translation for (scalePages)

* Translation for (removeCertSign)

* Translation for (removeAnnotations)

* Translation for (sign)

* Translation for (flatten)

* Translation for (extractImages)

* Translation for (merge)

* Translation for (view pdf)

* Translation for (pageRemover)

* Translation for (rotate)

* az Translation for replace-invert-color

* az Translation for addstamprequest

* Translation for (remove-image)

* Translation for #url-to-pdf

* Update messages_az_AZ.properties

* Update README.md

* Update README.md

* Update messages_az_AZ.properties

---------

Co-authored-by: NureddinFarzaliyev <nureddin.fa@gmail.com>
Co-authored-by: Lucifer25x <lucifer25x@protonmail.com>
Co-authored-by: islamd7 <vusal04999@gmail.com>
Co-authored-by: Valida Rahmanova <validerehmanova04@gmail.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-22 08:44:48 +00:00
albanobattistella
bab2052a60 Update messages_it_IT.properties (#2289)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-22 08:37:31 +00:00
Ludy
7773df7443 Fix: Convert a single string in the array to a list array (#2293) 2024-11-21 21:18:41 +00:00
Ludy
32d575b4e9 try to reduce the permission; update only translation files (#2291) 2024-11-21 20:00:12 +00:00
reecebrowne
4ebeedc028 Hover tools tooltips (#2290)
* Multi-tool advert on pages that share functionality

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* Rtl CSS

* Upgraded tooltips on multitool. Order selected pages list. Repositionicons. Minor additional tweaks

* restore gb translations

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* remove blankspace

* Restore hover tooltips

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

---------

Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-21 19:33:19 +00:00
reecebrowne
b4b005bc2e Feature/ux improvements (#2288)
* Multi-tool advert on pages that share functionality

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* Rtl CSS

* Upgraded tooltips on multitool. Order selected pages list. Repositionicons. Minor additional tweaks

* restore gb translations

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* remove blankspace

---------

Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-21 17:34:50 +00:00
Omar Ahmed Hassan
b92bcfe915 Update Arabic language (#2282) 2024-11-21 14:54:05 +00:00
Omar Ahmed Hassan
aeca2b23d9 Fix: Expand and de-clutter menus for matching search results in homepage #2264 (#2277)
* Hide empty menus that don't match search criteria

- Hide empty menus (accordions/feature groups) that don't match search criteria.

* Expand menus automatically for matching search results

- Fix a bug where menus (accordions/feature groups) did not automatically expand on the homepage when search results matched.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-21 14:24:45 +00:00
Anthony Stirling
68349c4426 Update downloader.js 2024-11-21 11:47:47 +00:00
Ludy
0f6d5e5a41 Note for PR creators added (#2279) 2024-11-21 11:31:32 +00:00
Ludy
df1c5476d9 Update German language (#2276) 2024-11-20 19:45:44 +00:00
Anthony Stirling
d0d6a70250 Metrics changes (#2273)
* Update downloader.js

* Update downloader.js

* Update common.html

* Update downloader.js
2024-11-20 10:32:44 +00:00
Anthony Stirling
a6ae3734ca Frooodle patch 8 (#2275)
* Update check_properties.yml

* Update messages_en_GB.properties

* Update messages_en_GB.properties
2024-11-20 09:44:51 +00:00
Anthony Stirling
c239d95131 Update PR-Demo-cleanup.yml 2024-11-20 08:41:29 +00:00
Anthony Stirling
d591874da6 Update PR-Demo-cleanup.yml 2024-11-20 08:28:51 +00:00
Anthony Stirling
6c623d8d84 Update MetricsAggregatorService.java (#2272) 2024-11-20 08:20:01 +00:00
Rafael Encinas
e059caa14e Fix id typo for "cropPdfCanvas" querySelector (#2271)
Fix id typo
2024-11-20 07:53:14 +00:00
dependabot[bot]
8eab35761d Bump org.projectlombok:lombok from 1.18.34 to 1.18.36 (#2266)
Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.34 to 1.18.36.
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.34...v1.18.36)

---
updated-dependencies:
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 23:16:31 +00:00
Anthony Stirling
c43af24ffe Update PR-Demo-Comment.yml 2024-11-17 16:17:44 +00:00
Anthony Stirling
e1b3cc736c Update PR-Demo-Comment.yml 2024-11-17 15:54:50 +00:00
Anthony Stirling
0fb9e18636 Update PR-Demo-Comment.yml 2024-11-17 15:53:29 +00:00
Ludy
5e1aac0b84 Read login data from application.properties (#2263)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-17 14:08:41 +00:00
Anthony Stirling
60bf649260 Update and rename PR-Demos.yml to PR-Demo-cleanup.yml 2024-11-17 13:43:48 +00:00
Anthony Stirling
a58696a38e Create PR-Demo-Comment.yml (#2261)
* Create PR-Demo-Comment.yml

* Update PR-Demo-Comment.yml
2024-11-17 13:32:14 +00:00
Ludy
44abc67678 shows the titles of the buttons (#2262)
* shows the titles of the buttons

* Update navbar.css
2024-11-17 12:33:41 +00:00
Anthony Stirling
d1e690ff8d Update PR-Demos.yml 2024-11-17 10:13:59 +00:00
Anthony Stirling
5dc8fa08ee Create PR-Demos.yml (#2260)
* Create PR-Demos.yml

* Update build.gradle

* Update build.gradle

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml

* Update PR-Demos.yml
2024-11-16 22:24:00 +00:00
Anthony Stirling
db028dfe27 docker move 2024-11-16 11:31:26 +00:00
Anthony Stirling
c24c504350 Delete Jenkinsfile 2024-11-16 11:22:17 +00:00
Anthony Stirling
5dcfe64d1c Update README.md 2024-11-16 11:11:12 +00:00
Anthony Stirling
d843696703 Update README.md 2024-11-16 11:07:54 +00:00
Dimitris Kaitantzidis
67de8a9460 Fix canvas pdf to csv (#2228)
* WIP: fixes canvas and rect to crop - small problem in smaller screens - neew to fix re render page on resize

* Closes #2209

* Closes #2227
2024-11-16 11:02:20 +00:00
Anthony Stirling
b26aa3417e Update build.gradle 2024-11-16 11:01:44 +00:00
Renan
8dfb5940ca Fixing bug: Add Image makes random changes to image (#2246) (#2256)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-16 08:55:40 +00:00
Anthony Stirling
0ce479e1e3 Update push-docker.yml 2024-11-16 08:43:42 +00:00
Anthony Stirling
cca3b6b525 Update multi-tool.html 2024-11-16 08:40:34 +00:00
github-actions[bot]
03529567ba 📝 Update README: Translation Progress Table (#2254)
📝 Sync README
> Made via sync_files.yml

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-15 22:18:01 +00:00
Anthony Stirling
781a52c759 Update ignore_translation.toml 2024-11-15 22:15:36 +00:00
Anthony Stirling
be2c103065 Update build.gradle 2024-11-15 21:56:57 +00:00
albanobattistella
80fd2eff5f Update messages_it_IT.properties (#2250)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2024-11-15 21:53:39 +00:00
github-actions[bot]
65abfd9c7a Update translation files (#2252)
Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
2024-11-15 21:52:28 +00:00
Anthony Stirling
f4082e3f96 Update PasswordController.java 2024-07-07 22:51:59 +01:00
Anthony Stirling
c93a48b40d Merge branch 'main' into decrypt 2024-07-07 22:50:41 +01:00
Anthony Stirling
75e10efcbd auto decrypt, update discord, fix multi file support for some inputs 2024-07-07 22:49:21 +01:00
317 changed files with 43197 additions and 6642 deletions

2
.github/CODEOWNERS vendored
View File

@@ -1,2 +1,2 @@
# All PRs to V1 must be approved by Frooodle
* @Frooodle
* @Frooodle @reecebrowne @Ludy87 @DarioGii

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: 💬 Discord Server
url: https://discord.gg/Cn8pWhQRxZ
url: https://discord.gg/HYmhKj45pU
about: You can join our Discord server for real time discussion and support

View File

@@ -11,7 +11,13 @@ updates:
interval: "weekly"
open-pull-requests-limit: 10
rebase-strategy: "auto"
- package-ecosystem: "docker"
directory: "/" # Location of Dockerfile
schedule:
interval: "weekly"
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly

View File

@@ -30,6 +30,8 @@ Security:
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/provider/**/*'
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/model/AuthenticationType.java'
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
- any-glob-to-any-file: '.github/workflows/dependency-review.yml'
- any-glob-to-any-file: '.github/workflows/scorecards.yml'
API:
- changed-files:
@@ -37,6 +39,7 @@ API:
- any-glob-to-any-file: 'src/main/java/stirling/software/SPDF/controller/api/**/*'
- any-glob-to-any-file: 'scripts/png_to_webp.py'
- any-glob-to-any-file: 'split_photos.py'
- any-glob-to-any-file: '.github/workflows/swagger.yml'
Documentation:
- changed-files:
@@ -57,6 +60,9 @@ Test:
- changed-files:
- any-glob-to-any-file: 'cucumber/**/*'
- any-glob-to-any-file: 'src/test**/*'
- any-glob-to-any-file: '.pre-commit-config'
- any-glob-to-any-file: '.github/workflows/pre_commit.yml'
- any-glob-to-any-file: '.github/workflows/scorecards.yml'
Github:
- changed-files:

4
.github/release.yml vendored
View File

@@ -9,7 +9,7 @@ changelog:
- title: Bug Fixes
labels:
- Bug
- title: Enhancements
labels:
- enhancement
@@ -26,7 +26,7 @@ changelog:
- title: Translation Changes
labels:
- Translation
- title: Other Changes
labels:
- "*"

View File

@@ -9,8 +9,9 @@ The script also provides functionality to update the translation files to match
adjusting the format.
Usage:
python script_name.py --reference-file <path_to_reference_file> --branch <branch_name> [--files <list_of_changed_files>]
python check_language_properties.py --reference-file <path_to_reference_file> --branch <branch_name> [--actor <actor_name>] [--files <list_of_changed_files>]
"""
import copy
import glob
import os
@@ -18,6 +19,10 @@ import argparse
import re
# Maximum size for properties files (e.g., 200 KB)
MAX_FILE_SIZE = 200 * 1024
def parse_properties_file(file_path):
"""Parses a .properties file and returns a list of objects (including comments, empty lines, and line numbers)."""
properties_list = []
@@ -95,7 +100,7 @@ def write_json_file(file_path, updated_properties):
def update_missing_keys(reference_file, file_list, branch=""):
reference_properties = parse_properties_file(reference_file)
for file_path in file_list:
basename_current_file = os.path.basename(branch + file_path)
basename_current_file = os.path.basename(os.path.join(branch, file_path))
if (
basename_current_file == os.path.basename(reference_file)
or not file_path.endswith(".properties")
@@ -103,7 +108,7 @@ def update_missing_keys(reference_file, file_list, branch=""):
):
continue
current_properties = parse_properties_file(branch + file_path)
current_properties = parse_properties_file(os.path.join(branch, file_path))
updated_properties = []
for ref_entry in reference_properties:
ref_entry_copy = copy.deepcopy(ref_entry)
@@ -114,60 +119,79 @@ def update_missing_keys(reference_file, file_list, branch=""):
if ref_entry_copy["key"] == current_entry["key"]:
ref_entry_copy["value"] = current_entry["value"]
updated_properties.append(ref_entry_copy)
write_json_file(branch + file_path, updated_properties)
write_json_file(os.path.join(branch, file_path), updated_properties)
def check_for_missing_keys(reference_file, file_list, branch):
update_missing_keys(reference_file, file_list, branch + "/")
update_missing_keys(reference_file, file_list, branch)
def read_properties(file_path):
with open(file_path, "r", encoding="utf-8") as file:
return file.read().splitlines()
if os.path.isfile(file_path) and os.path.exists(file_path):
with open(file_path, "r", encoding="utf-8") as file:
return file.read().splitlines()
return [""]
def check_for_differences(reference_file, file_list, branch):
def check_for_differences(reference_file, file_list, branch, actor):
reference_branch = reference_file.split("/")[0]
basename_reference_file = os.path.basename(reference_file)
report = []
report.append(
f"### 📋 Checking with the file `{basename_reference_file}` from the `{reference_branch}` - Checking the `{branch}`"
)
report.append(f"#### 🔄 Reference Branch: `{reference_branch}`")
reference_lines = read_properties(reference_file)
has_differences = False
only_reference_file = True
for file_path in file_list:
basename_current_file = os.path.basename(branch + "/" + file_path)
file_arr = file_list
if len(file_list) == 1:
file_arr = file_list[0].split()
base_dir = os.path.abspath(os.path.join(os.getcwd(), "src", "main", "resources"))
for file_path in file_arr:
absolute_path = os.path.abspath(file_path)
# Verify that file is within the expected directory
if not absolute_path.startswith(base_dir):
raise ValueError(f"Unsafe file found: {file_path}")
# Verify file size before processing
if os.path.getsize(os.path.join(branch, file_path)) > MAX_FILE_SIZE:
raise ValueError(
f"The file {file_path} is too large and could pose a security risk."
)
basename_current_file = os.path.basename(os.path.join(branch, file_path))
if (
basename_current_file == basename_reference_file
or not file_path.startswith(
os.path.join("src", "main", "resources", "messages_")
)
or not file_path.endswith(".properties")
or not basename_current_file.startswith("messages_")
):
continue
only_reference_file = False
report.append(f"#### 🗂️ **Checking File:** `{basename_current_file}`...")
current_lines = read_properties(branch + "/" + file_path)
report.append(f"#### 📃 **File Check:** `{basename_current_file}`")
current_lines = read_properties(os.path.join(branch, file_path))
reference_line_count = len(reference_lines)
current_line_count = len(current_lines)
if reference_line_count != current_line_count:
report.append("")
report.append("- **Test 1 Status:** ❌ Failed")
report.append("1. **Test Status:** ❌ **_Failed_**")
report.append(" - **Issue:**")
has_differences = True
if reference_line_count > current_line_count:
report.append(
f" - **Issue:** Missing lines! Comments, empty lines, or translation strings are missing. Details: {reference_line_count} (reference) vs {current_line_count} (current)."
f" - **_Mismatched line count_**: {reference_line_count} (reference) vs {current_line_count} (current). Comments, empty lines, or translation strings are missing."
)
elif reference_line_count < current_line_count:
report.append(
f" - **Issue:** Too many lines! Check your translation files! Details: {reference_line_count} (reference) vs {current_line_count} (current)."
f" - **_Too many lines_**: {reference_line_count} (reference) vs {current_line_count} (current). Please verify if there is an additional line that needs to be removed."
)
# update_missing_keys(reference_file, [file_path], branch + "/")
else:
report.append("- **Test 1 Status:** ✅ Passed")
report.append("1. **Test Status:** ✅ **_Passed_**")
# Check for missing or extra keys
current_keys = []
@@ -192,32 +216,42 @@ def check_for_differences(reference_file, file_list, branch):
has_differences = True
missing_keys_str = "`, `".join(missing_keys_list)
extra_keys_str = "`, `".join(extra_keys_list)
report.append("- **Test 2 Status:** ❌ Failed")
report.append("2. **Test Status:** ❌ **_Failed_**")
report.append(" - **Issue:**")
if missing_keys_list:
spaces_keys_list = []
for key in missing_keys_list:
if " " in key:
spaces_keys_list.append(key)
if spaces_keys_list:
spaces_keys_str = "`, `".join(spaces_keys_list)
report.append(
f" - **_Keys containing unnecessary spaces_**: `{spaces_keys_str}`!"
)
report.append(
f" - **Issue:** There are keys in ***{basename_current_file}*** `{missing_keys_str}` that are not present in ***{basename_reference_file}***!"
f" - **_Extra keys in `{basename_current_file}`_**: `{missing_keys_str}` that are not present in **_`{basename_reference_file}`_**."
)
if extra_keys_list:
report.append(
f" - **Issue:** There are keys in ***{basename_reference_file}*** `{extra_keys_str}` that are not present in ***{basename_current_file}***!"
f" - **_Missing keys in `{basename_reference_file}`_**: `{extra_keys_str}` that are not present in **_`{basename_current_file}`_**."
)
# update_missing_keys(reference_file, [file_path], branch + "/")
else:
report.append("- **Test 2 Status:** ✅ Passed")
# if has_differences:
# report.append("")
# report.append(f"#### 🚧 ***{basename_current_file}*** will be corrected...")
report.append("2. **Test Status:** ✅ **_Passed_**")
report.append("")
report.append("---")
report.append("")
# update_file_list = glob.glob(branch + "/src/**/messages_*.properties", recursive=True)
# update_missing_keys(reference_file, update_file_list)
# report.append("---")
# report.append("")
if has_differences:
report.append("## ❌ Overall Check Status: **_Failed_**")
report.append("")
report.append(
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/src/main/resources/messages_en_GB.properties)"
)
else:
report.append("## ✅ Overall Check Status: **_Success_**")
report.append("")
report.append(
f"Thanks @{actor} for your help in keeping the translations up to date."
)
if not only_reference_file:
print("\n".join(report))
@@ -225,6 +259,11 @@ def check_for_differences(reference_file, file_list, branch):
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Find missing keys")
parser.add_argument(
"--actor",
required=False,
help="Actor from PR.",
)
parser.add_argument(
"--reference-file",
required=True,
@@ -244,11 +283,21 @@ if __name__ == "__main__":
)
args = parser.parse_args()
# Sanitize --actor input to avoid injection attacks
if args.actor:
args.actor = re.sub(r"[^a-zA-Z0-9_\\-]", "", args.actor)
# Sanitize --branch input to avoid injection attacks
if args.branch:
args.branch = re.sub(r"[^a-zA-Z0-9\\-]", "", args.branch)
file_list = args.files
if file_list is None:
file_list = glob.glob(
os.getcwd() + "/src/**/messages_*.properties", recursive=True
os.path.join(
os.getcwd(), "src", "main", "resources", "messages_*.properties"
)
)
update_missing_keys(args.reference_file, file_list)
else:
check_for_differences(args.reference_file, file_list, args.branch)
check_for_differences(args.reference_file, file_list, args.branch, args.actor)

189
.github/workflows/PR-Demo-Comment.yml vendored Normal file
View File

@@ -0,0 +1,189 @@
name: PR Deployment via Comment
on:
issue_comment:
types: [created]
jobs:
check-comment:
runs-on: ubuntu-latest
if: |
github.event.issue.pull_request &&
(
contains(github.event.comment.body, 'prdeploy') ||
contains(github.event.comment.body, 'deploypr')
)
&&
(
github.event.comment.user.login == 'frooodle' ||
github.event.comment.user.login == 'sf298' ||
github.event.comment.user.login == 'Ludy87' ||
github.event.comment.user.login == 'LaserKaspar' ||
github.event.comment.user.login == 'sbplat' ||
github.event.comment.user.login == 'reecebrowne'
)
outputs:
pr_number: ${{ steps.get-pr.outputs.pr_number }}
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Get PR data
id: get-pr
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const prNumber = context.payload.issue.number;
console.log(`PR Number: ${prNumber}`);
core.setOutput('pr_number', prNumber);
- name: Get PR repository and ref
id: get-pr-info
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const { owner, repo } = context.repo;
const prNumber = context.payload.issue.number;
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber,
});
// For forks, use the full repository name, for internal PRs use the current repo
const repository = pr.head.repo.fork ? pr.head.repo.full_name : `${owner}/${repo}`;
console.log(`PR Repository: ${repository}`);
console.log(`PR Branch: ${pr.head.ref}`);
core.setOutput('repository', repository);
core.setOutput('ref', pr.head.ref);
deploy-pr:
needs: check-comment
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: ${{ needs.check-comment.outputs.pr_repository }}
ref: ${{ needs.check-comment.outputs.pr_ref }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up JDK
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: '17'
distribution: 'temurin'
- name: Run Gradle Command
run: ./gradlew clean build
env:
DOCKER_ENABLE_SECURITY: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push PR-specific image
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Deploy to VPS
run: |
# First create the docker-compose content locally
cat > docker-compose.yml << 'EOF'
version: '3.3'
services:
stirling-pdf:
container_name: stirling-pdf-pr-${{ needs.check-comment.outputs.pr_number }}
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
ports:
- "${{ needs.check-comment.outputs.pr_number }}:8080"
volumes:
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/data:/usr/share/tessdata:rw
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/config:/configs:rw
- /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-GB
UI_APPNAME: "Stirling-PDF PR#${{ needs.check-comment.outputs.pr_number }}"
UI_HOMEDESCRIPTION: "PR#${{ needs.check-comment.outputs.pr_number }} for Stirling-PDF Latest"
UI_APPNAMENAVBAR: "PR#${{ needs.check-comment.outputs.pr_number }}"
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
restart: on-failure:5
EOF
# Then copy the file and execute commands
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/docker-compose.yml
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
# Create PR-specific directories
mkdir -p /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/{data,config,logs}
# Move docker-compose file to correct location
mv /tmp/docker-compose.yml /stirling/PR-${{ needs.check-comment.outputs.pr_number }}/docker-compose.yml
# Start or restart the container
cd /stirling/PR-${{ needs.check-comment.outputs.pr_number }}
docker-compose pull
docker-compose up -d
ENDSSH
- name: Post deployment URL to PR
if: success()
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const { GITHUB_REPOSITORY } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = ${{ needs.check-comment.outputs.pr_number }};
const deploymentUrl = `http://${{ secrets.VPS_HOST }}:${prNumber}`;
const commentBody = `## 🚀 PR Test Deployment\n\n` +
`Your PR has been deployed for testing!\n\n` +
`🔗 **Test URL:** [${deploymentUrl}](${deploymentUrl})\n\n` +
`This deployment will be automatically cleaned up when the PR is closed.\n\n`;
await github.rest.issues.createComment({
owner: repoOwner,
repo: repoName,
issue_number: prNumber,
body: commentBody
});

84
.github/workflows/PR-Demo-cleanup.yml vendored Normal file
View File

@@ -0,0 +1,84 @@
name: PR Deployment cleanup
on:
pull_request:
types: [opened, synchronize, reopened, closed]
permissions: read-all
env:
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets
CLEANUP_PERFORMED: 'false' # Add flag to track if cleanup occurred
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
if: github.event.action == 'closed'
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Cleanup PR deployment
id: cleanup
run: |
CLEANUP_STATUS=$(ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
if [ -d "/stirling/PR-${{ github.event.pull_request.number }}" ]; then
echo "Found PR directory, proceeding with cleanup..."
# Stop and remove containers
cd /stirling/PR-${{ github.event.pull_request.number }}
docker-compose down || true
# Go back to root before removal
cd /
# Remove PR-specific directories
rm -rf /stirling/PR-${{ github.event.pull_request.number }}
# Remove the Docker image
docker rmi --no-prune ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ github.event.pull_request.number }} || true
echo "PERFORMED_CLEANUP"
else
echo "PR directory not found, nothing to clean up"
echo "NO_CLEANUP_NEEDED"
fi
ENDSSH
)
if [[ $CLEANUP_STATUS == *"PERFORMED_CLEANUP"* ]]; then
echo "cleanup_performed=true" >> $GITHUB_OUTPUT
else
echo "cleanup_performed=false" >> $GITHUB_OUTPUT
fi
- name: Post cleanup notice to PR
if: steps.cleanup.outputs.cleanup_performed == 'true'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const { GITHUB_REPOSITORY } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = context.issue.number;
const commentBody = `## 🧹 Deployment Cleanup\n\n` +
`The test deployment for this PR has been cleaned up.`;
await github.rest.issues.createComment({
owner: repoOwner,
repo: repoName,
issue_number: prNumber,
body: commentBody
});

View File

@@ -3,17 +3,23 @@ on:
pull_request_target:
types: [opened, synchronize]
permissions: read-all
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Apply Labels
uses: actions/labeler@v5
uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/labeler-config.yml

View File

@@ -6,13 +6,13 @@ on:
pull_request:
branches: ["main"]
permissions: read-all
jobs:
build:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
@@ -21,23 +21,30 @@ jobs:
jdk-version: [17, 21]
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK ${{ matrix.jdk-version }}
uses: actions/setup-java@v4
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: ${{ matrix.jdk-version }}
distribution: "temurin"
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: 8.7
- name: Build with Gradle
run: ./gradlew build --no-build-cache
- name: Build with Gradle and no spring security
run: ./gradlew clean build
env:
DOCKER_ENABLE_SECURITY: false
- name: Build with Gradle and with spring security
run: ./gradlew clean build
env:
DOCKER_ENABLE_SECURITY: true
docker-compose-tests:
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
# (github.event_name == 'pull_request' &&
@@ -56,17 +63,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: "17"
distribution: "adopt"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- name: Install Docker Compose
run: |
@@ -74,9 +86,9 @@ jobs:
sudo chmod +x /usr/local/bin/docker-compose
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.7"
python-version: "3.12"
- name: Pip requirements
run: |

View File

@@ -1,40 +1,40 @@
name: Check Properties Files
name: Check Properties Files on PR
on:
pull_request_target:
types: [opened, synchronize, reopened]
paths:
- "src/main/resources/messages_*.properties"
push:
paths:
- "src/main/resources/messages_en_GB.properties"
permissions:
contents: write
pull-requests: write
permissions: read-all
jobs:
check-files:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Checkout main branch first
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: main
path: main-branch
fetch-depth: 0
- name: Checkout PR branch
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
path: pr-branch
fetch-depth: 0
- name: Checkout main branch
uses: actions/checkout@v4
with:
ref: main
path: main-branch
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.x"
@@ -49,56 +49,73 @@ jobs:
echo "Fetching PR changed files..."
cd pr-branch
gh repo set-default ${{ github.repository }}
gh pr view ${{ github.event.pull_request.number }} --json files -q ".files[].path" > ../changed_files.txt
# Store files in a safe way, only allowing valid properties files
echo "Getting list of changed files from PR..."
gh pr view ${{ github.event.pull_request.number }} --json files -q ".files[].path" | grep -E '^src/main/resources/messages_[a-zA-Z_]+\.properties$' > ../changed_files.txt
cd ..
echo $(cat changed_files.txt)
BRANCH_PATH="pr-branch"
echo "BRANCH_PATH=${BRANCH_PATH}" >> $GITHUB_ENV
CHANGED_FILES=$(cat changed_files.txt | tr '\n' ' ')
echo "CHANGED_FILES=${CHANGED_FILES}" >> $GITHUB_ENV
echo "Changed files: ${CHANGED_FILES}"
echo "Branch: ${BRANCH_PATH}"
echo "Processing changed files..."
mapfile -t CHANGED_FILES < changed_files.txt
CHANGED_FILES_STR="${CHANGED_FILES[*]}"
echo "CHANGED_FILES=${CHANGED_FILES_STR}" >> $GITHUB_ENV
echo "Changed files: ${CHANGED_FILES_STR}"
- name: Determine reference file
id: determine-file
run: |
echo "Determining reference file..."
if echo "${{ env.CHANGED_FILES }}" | grep -q 'src/main/resources/messages_en_GB.properties'; then
if grep -Fxq "src/main/resources/messages_en_GB.properties" changed_files.txt; then
echo "Using PR branch reference file"
echo "REFERENCE_FILE=pr-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV
else
echo "Using main branch reference file"
echo "REFERENCE_FILE=main-branch/src/main/resources/messages_en_GB.properties" >> $GITHUB_ENV
fi
echo "REFERENCE_FILE=${{ env.REFERENCE_FILE }}"
- name: Show REFERENCE_FILE
run: echo "Reference file is set to ${{ env.REFERENCE_FILE }}"
run: echo "Reference file is set to ${REFERENCE_FILE}"
- name: Run Python script to check files
id: run-check
run: |
python main-branch/.github/scripts/check_language_properties.py --reference-file ${{ env.REFERENCE_FILE }} --branch ${{ env.BRANCH_PATH }} --files ${{ env.CHANGED_FILES }} > failure.txt || true
echo "Running Python script to check files..."
python main-branch/.github/scripts/check_language_properties.py \
--actor ${{ github.event.pull_request.user.login }} \
--reference-file "${REFERENCE_FILE}" \
--branch pr-branch \
--files "${CHANGED_FILES[@]}" > result.txt || true
- name: Capture output
id: capture-output
run: |
if [ -f failure.txt ] && [ -s failure.txt ]; then
echo "Test failed, capturing output..."
ERROR_OUTPUT=$(cat failure.txt)
echo "ERROR_OUTPUT<<EOF" >> $GITHUB_ENV
echo "$ERROR_OUTPUT" >> $GITHUB_ENV
if [ -f result.txt ] && [ -s result.txt ]; then
echo "Test, capturing output..."
SCRIPT_OUTPUT=$(cat result.txt)
echo "SCRIPT_OUTPUT<<EOF" >> $GITHUB_ENV
echo "$SCRIPT_OUTPUT" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
echo $ERROR_OUTPUT
echo "${SCRIPT_OUTPUT}"
# Set FAIL_JOB to true if SCRIPT_OUTPUT contains ❌
if [[ "$SCRIPT_OUTPUT" == *"❌"* ]]; then
echo "FAIL_JOB=true" >> $GITHUB_ENV
else
echo "FAIL_JOB=false" >> $GITHUB_ENV
fi
else
echo "No errors found."
echo "ERROR_OUTPUT=" >> $GITHUB_ENV
echo "No update found."
echo "SCRIPT_OUTPUT=" >> $GITHUB_ENV
echo "FAIL_JOB=false" >> $GITHUB_ENV
fi
- name: Post comment on PR
if: env.ERROR_OUTPUT != ''
uses: actions/github-script@v7
if: env.SCRIPT_OUTPUT != ''
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const { GITHUB_REPOSITORY, ERROR_OUTPUT } = process.env;
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = context.issue.number;
@@ -120,7 +137,7 @@ jobs:
owner: repoOwner,
repo: repoName,
comment_id: comment.id,
body: `## 🚀 Translation Verification Summary\n\n\n${ERROR_OUTPUT}\n`
body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n`
});
console.log("Updated existing comment.");
} else if (!comment) {
@@ -129,74 +146,15 @@ jobs:
owner: repoOwner,
repo: repoName,
issue_number: prNumber,
body: `## 🚀 Translation Verification Summary\n\n\n${ERROR_OUTPUT}\n`
body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n`
});
console.log("Created new comment.");
} else {
console.log("Comment update attempt denied. Actor does not match.");
}
# - name: Set up git config
# run: |
# git config --global user.name "github-actions[bot]"
# git config --global user.email "github-actions[bot]@users.noreply.github.com"
# - name: Add translation keys
# run: |
# cd ${{ env.BRANCH_PATH }}
# git add src/main/resources/messages_*.properties
# git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
# git commit -m "Update translation files" || echo "No changes to commit"
# - name: Push
# if: env.CHANGES_DETECTED == 'true'
# run: |
# cd pr-branch
# git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.event.pull_request.head.repo.full_name }}.git
# git push origin ${{ github.head_ref }} || echo "Push failed: possibly no changes to push"
update-translations-main:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Run Python script to check files
id: run-check
- name: Fail job if errors found
if: env.FAIL_JOB == 'true'
run: |
python .github/scripts/check_language_properties.py --reference-file src/main/resources/messages_en_GB.properties --branch main
- name: Set up git config
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Add translation keys
run: |
git add src/main/resources/messages_*.properties
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
- name: Create Pull Request
id: cpr
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update translation files"
committer: GitHub Action <action@github.com>
author: GitHub Action <action@github.com>
signoff: true
branch: update_translation_files
title: "Update translation files"
body: |
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
labels: Translation
draft: false
delete-branch: true
echo "Failing the job because errors were detected."
exit 1

79
.github/workflows/codeql.yml-disabled vendored Normal file
View File

@@ -0,0 +1,79 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
#disable for now
#on:
# push:
# branches: ["main"]
# pull_request:
# The branches below must be a subset of the branches above
# branches: ["main"]
# schedule:
# - cron: "0 0 * * 1"
permissions:
contents: read
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["java"]
# CodeQL supports [ $supported-codeql-languages ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
with:
category: "/language:${{matrix.language}}"

27
.github/workflows/dependency-review.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request,
# surfacing known-vulnerable versions of the packages declared or updated in the PR.
# Once installed, if the workflow run is marked as required,
# PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: 'Checkout Repository'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: 'Dependency Review'
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0

View File

@@ -7,25 +7,30 @@ on:
paths:
- "build.gradle"
permissions:
contents: write
pull-requests: write
permissions: read-all
jobs:
generate-license-report:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: "17"
distribution: "adopt"
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: Run Gradle Command
run: ./gradlew clean generateLicenseReport
@@ -47,7 +52,7 @@ jobs:
- name: Create Pull Request
id: cpr
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update 3rd Party Licenses"
@@ -60,9 +65,10 @@ jobs:
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
labels: licenses
labels: licenses,github-actions
draft: false
delete-branch: true
sign-commits: true
- name: Auto approve
if: steps.cpr.outputs.pull-request-operation == 'created'
@@ -72,7 +78,7 @@ jobs:
- name: Enable auto-merge
if: steps.cpr.outputs.pull-request-operation == 'created'
uses: peter-evans/enable-pull-request-automerge@v3
uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}

View File

@@ -4,21 +4,26 @@ on:
schedule:
- cron: "30 20 * * *"
permissions:
contents: read
issues: write
permissions: read-all
jobs:
labeler:
name: Labeler
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Check out the repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run Labeler
uses: crazy-max/ghaction-github-labeler@v5
uses: crazy-max/ghaction-github-labeler@b54af0c25861143e7c8813d7cbbf46d2c341680c # v5.1.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
yaml-file: .github/labels.yml
skip-delete: true
skip-delete: true

104
.github/workflows/multiOSReleases.yml vendored Normal file
View File

@@ -0,0 +1,104 @@
name: Test Installers Build
on:
workflow_dispatch:
release:
types: [created]
permissions: read-all
jobs:
build-installers:
strategy:
matrix:
include:
- os: windows-latest
platform: win
ext: exe
#- os: macos-latest
# platform: mac
# ext: dmg
#- os: ubuntu-latest
# platform: linux
# ext: deb
runs-on: ${{ matrix.os }}
permissions:
contents: write
packages: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 21
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: "21"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
gradle-version: 8.7
# Install Windows dependencies
- name: Install WiX Toolset
if: matrix.os == 'windows-latest'
run: |
curl -L -o wix.exe https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe
.\wix.exe /install /quiet
# Install Linux dependencies
- name: Install Linux Dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y fakeroot rpm
# Get version number
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
shell: bash
- name: Get version number mac
id: versionNumberMac
run: echo "versionNumberMac=$(./gradlew printMacVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
shell: bash
# Build installer
- name: Build Installer
run: ./gradlew build jpackage -x test --info
env:
DOCKER_ENABLE_SECURITY: false
STIRLING_PDF_DESKTOP_UI: true
# Rename and collect artifacts based on OS
- name: Prepare artifacts
id: prepare
shell: bash
run: |
if [ "${{ matrix.os }}" = "windows-latest" ]; then
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.exe" "Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}"
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumberMac.outputs.versionNumberMac }}.dmg" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
else
mv "build/jpackage/stirling-pdf_${{ steps.versionNumber.outputs.versionNumber }}-1_amd64.deb" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
fi
# Upload installer as artifact for testing
- name: Upload Installer Artifact
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
path: Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}
retention-days: 1
if-no-files-found: error
- name: Upload binaries to release
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
with:
files: ./Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}

54
.github/workflows/pre_commit.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Pre-commit
on:
push:
branches: [main]
permissions: read-all
jobs:
update:
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Run Pre-Commit Hooks
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
continue-on-error: true
- name: Set up git config
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: git add
run: |
git add .
git diff --staged --quiet || git commit -m ":file_folder: pre-commit
> Made via .github/workflows/pre_commit.yml" || echo "pre-commit: no changes"
- name: Create Pull Request
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "ci: 🤖 format everything with pre-commit"
committer: GitHub Action <action@github.com>
author: GitHub Action <action@github.com>
signoff: true
branch: pre-commit
title: "🤖 format everything with pre-commit by <github-actions[bot]>"
body: |
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
draft: false
delete-branch: true
labels: github-actions
sign-commits: true

View File

@@ -10,20 +10,28 @@ on:
permissions:
contents: read
packages: write
id-token: write
jobs:
push:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- uses: actions/checkout@v4
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
gradle-version: 8.7
@@ -32,29 +40,34 @@ jobs:
env:
DOCKER_ENABLE_SECURITY: false
- name: Install cosign
uses: sigstore/cosign-installer@v3.7.0
with:
cosign-release: 'v2.4.1'
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
- name: Convert repository owner to lowercase
id: repoowner
@@ -62,11 +75,12 @@ jobs:
- name: Generate tags
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1
with:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
@@ -74,7 +88,8 @@ jobs:
type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push main Dockerfile
uses: docker/build-push-action@v6
id: build-push-regular
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
@@ -86,26 +101,57 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8
provenance: true
sbom: true
- name: Sign regular images
env:
DIGEST: ${{ steps.build-push-regular.outputs.digest }}
TAGS: ${{ steps.meta.outputs.tags }}
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
# Always sign images regardless of branch
echo "$TAGS" | tr ',' '\n' | while read -r tag; do
cosign sign --yes \
--key env://COSIGN_PRIVATE_KEY \
"${tag}@${DIGEST}"
done
# For alpha builds specifically, we want to ensure they're marked as development builds
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "Signing alpha build with development attestation"
echo "$TAGS" | tr ',' '\n' | while read -r tag; do
if [[ $tag == *":alpha" ]]; then
cosign attest --key env://COSIGN_PRIVATE_KEY \
--predicate <(echo '{"type":"development"}') \
--yes "${tag}@${DIGEST}"
fi
done
fi
- name: Generate tags ultra-lite
id: meta2
uses: docker/metadata-action@v5
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1
if: github.ref != 'refs/heads/main'
with:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push Dockerfile-ultra-lite
uses: docker/build-push-action@v6
id: build-push-lite
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
if: github.ref != 'refs/heads/main'
with:
context: .
file: ./Dockerfile-ultra-lite
file: ./Dockerfile.ultra-lite
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -113,27 +159,43 @@ jobs:
labels: ${{ steps.meta2.outputs.labels }}
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8
provenance: true
sbom: true
- name: Sign ultra-lite images
if: github.ref != 'refs/heads/main'
env:
DIGEST: ${{ steps.build-push-lite.outputs.digest }}
TAGS: ${{ steps.meta2.outputs.tags }}
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
echo "$TAGS" | tr ',' '\n' | while read -r tag; do
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${tag}@${DIGEST}"
done
- name: Generate tags fat
id: meta3
uses: docker/metadata-action@v5
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1
if: github.ref != 'refs/heads/main'
with:
images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push main Dockerfile fat
uses: docker/build-push-action@v6
id: build-push-fat
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
if: github.ref != 'refs/heads/main'
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile-fat
file: ./Dockerfile.fat
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -141,3 +203,17 @@ jobs:
labels: ${{ steps.meta3.outputs.labels }}
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8
provenance: true
sbom: true
- name: Sign fat images
if: github.ref != 'refs/heads/main'
env:
DIGEST: ${{ steps.build-push-fat.outputs.digest }}
TAGS: ${{ steps.meta3.outputs.tags }}
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
echo "$TAGS" | tr ',' '\n' | while read -r tag; do
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${tag}@${DIGEST}"
done

View File

@@ -4,12 +4,15 @@ on:
workflow_dispatch:
release:
types: [created]
permissions:
contents: write
packages: write
permissions: read-all
jobs:
push:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
strategy:
matrix:
enable_security: [true, false]
@@ -19,15 +22,20 @@ jobs:
- enable_security: false
file_suffix: ""
steps:
- uses: actions/checkout@v4
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
gradle-version: 8.7
@@ -35,33 +43,34 @@ jobs:
run: ./gradlew clean createExe
env:
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
STIRLING_PDF_DESKTOP_UI: false
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Rename binarie
if: matrix.file_suffix != ''
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
- name: Upload Assets binarie
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
path: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
name: Stirling-PDF${{ matrix.file_suffix }}.exe
path: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
name: Stirling-PDF-Server${{ matrix.file_suffix }}.exe
overwrite: true
retention-days: 1
if-no-files-found: error
- name: Upload binaries to release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
with:
files: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
files: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
- name: Rename jar binaries
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
- name: Upload Assets jar binaries
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
path: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
name: Stirling-PDF${{ matrix.file_suffix }}.jar
@@ -70,6 +79,6 @@ jobs:
if-no-files-found: error
- name: Upload jar binaries to release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0
with:
files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar

79
.github/workflows/scorecards.yml vendored Normal file
View File

@@ -0,0 +1,79 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '20 7 * * 2'
push:
branches: ["main"]
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
contents: read
actions: read
# To allow GraphQL ListCommits to work
issues: read
pull-requests: read
# To detect SAST tools
checks: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecards on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
with:
sarif_file: results.sarif

View File

@@ -5,6 +5,8 @@ on:
- cron: "30 0 * * *"
workflow_dispatch:
permissions: read-all
jobs:
stale:
runs-on: ubuntu-latest
@@ -12,8 +14,13 @@ jobs:
issues: write
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: 30 days stale issues
uses: actions/stale@v9
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30

View File

@@ -6,19 +6,26 @@ on:
branches:
- master
permissions: read-all
jobs:
push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
- name: Generate Swagger documentation
run: ./gradlew generateOpenApiDocs

View File

@@ -9,17 +9,23 @@ on:
- "src/main/resources/messages_*.properties"
- "scripts/ignore_translation.toml"
permissions:
contents: write
pull-requests: write
permissions: read-all
jobs:
sync-readme:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.x"
- name: Install dependencies
@@ -36,7 +42,7 @@ jobs:
git diff --staged --quiet || git commit -m ":memo: Sync README
> Made via sync_files.yml" || echo "no changes"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update files
@@ -52,3 +58,4 @@ jobs:
draft: false
delete-branch: true
labels: Documentation,Translation,github-actions
sign-commits: true

View File

@@ -0,0 +1,71 @@
name: Update Translations
on:
push:
branches: ["main"]
paths:
- "src/main/resources/messages_en_GB.properties"
permissions: read-all
jobs:
update-translations-main:
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.x"
- name: Run Python script to check files
id: run-check
run: |
echo "Running Python script to check files..."
python .github/scripts/check_language_properties.py \
--reference-file src/main/resources/messages_en_GB.properties \
--branch main
- name: Set up git config
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Add translation keys
run: |
git add src/main/resources/messages_*.properties
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
- name: Create Pull Request
id: cpr
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update translation files"
committer: GitHub Action <action@github.com>
author: GitHub Action <action@github.com>
signoff: true
branch: update_translation_files
title: "Update translation files"
add-paths: |
src/main/resources/messages_*.properties
body: |
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
draft: false
delete-branch: true
labels: Translation,github-actions
sign-commits: true

1
.gitignore vendored
View File

@@ -161,3 +161,4 @@ out/
.pytest_cache
.ipynb_checkpoints
**/jcef-bundle/

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.1
rev: v0.8.4
hooks:
- id: ruff
args:
@@ -12,7 +12,7 @@ repos:
files: ^((.github/scripts|scripts)/.+)?[^/]+\.py$
exclude: (split_photos.py)
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
rev: v2.3.0
hooks:
- id: codespell
args:
@@ -21,6 +21,25 @@ repos:
- --quiet-level=2
files: \.(properties|html|css|js|py|md)$
exclude: (.vscode|.devcontainer|src/main/resources|Dockerfile)
- repo: https://github.com/gitleaks/gitleaks
rev: v8.22.0
hooks:
- id: gitleaks
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
files: ^.*(\.bash|\.sh|\.ksh|\.zsh)$
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: end-of-file-fixer
files: ^.*(\.js|\.java|\.py|\.yml)$
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
- id: trailing-whitespace
files: ^.*(\.js|\.java|\.py|\.yml)$
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
- repo: local
hooks:
- id: check-duplicate-properties-keys
@@ -28,12 +47,11 @@ repos:
entry: python .github/scripts/check_duplicates.py
language: python
files: ^(src)/.+\.properties$
- repo: local
hooks:
- id: check-html-tabs
name: Check HTML for tabs
description: Ensures HTML/CSS/JS files do not contain tab characters
# args: ["--replace_with= "]
entry: python .github/scripts/check_tabulator.py
language: python
exclude: ^(src/main/resources/static/pdfjs|src/main/resources/static/pdfjs-legacy)
files: ^.*(\.html|\.css|\.js)$
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js$)
files: ^.*(\.html|\.css|\.js)$

View File

@@ -49,5 +49,7 @@
"editor.indentSize": "tabSize",
"editor.stickyScroll.enabled": false,
"editor.minimap.enabled": false,
"editor.formatOnSave": true
"editor.formatOnSave": true,
"java.format.settings.google.mode": "jar-file",
"java.format.settings.google.extra": "--aosp --skip-sorting-imports"
}

View File

@@ -18,9 +18,9 @@ For a detailed pull request tutorial, see [this guide](https://www.digitalocean.
Please make sure your Pull Request adheres to the following guidelines:
- Use the PR template provided.
- Keep your Pull Request title succinct, detailed and to the point.
- Keep your Pull Request title succinct, detailed, and to the point.
- Keep commits atomic. One commit should contain one change. If you want to make multiple changes, submit multiple Pull Requests.
- Commits should be clear, concise and easy to understand.
- Commits should be clear, concise, and easy to understand.
- References to the Issue number in the Pull Request and/or Commit message.
## Translations
@@ -29,15 +29,15 @@ If you would like to add or modify a translation, please see [How to add new lan
## Docs
Documentation for Stirling-PDF is handled in a separate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://docs.stirlingpdf.com/](https://docs.stirlingpdf.com/).
Documentation for Stirling-PDF is handled in a separate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use the "edit this page"-button at the bottom of each page at [https://docs.stirlingpdf.com/](https://docs.stirlingpdf.com/).
## Fixing Bugs or Adding a New Feature
First, make sure you've read the section [Pull Requests](#pull-requests).
To build from source, please follow this [Guide](LocalRunGuide.md).
To build from the source, please follow this [Guide](LocalRunGuide.md).
If, at any point of time, you have a question, please feel free to ask in the same issue thread or in our [Discord](https://discord.gg/FJUSXUSYec).
If, at any point in time, you have a question, please feel free to ask in the same issue thread or in our [Discord](https://discord.gg/FJUSXUSYec).
## License

View File

@@ -1,12 +1,5 @@
# New Database Backup and Import Functionality
> [!IMPORTANT]
> **Full activation will take place on approximately January 5th, 2025!**
Why is the waiting time six months?
There are users who only install updates sporadically; if they skip the preparation, it can/will lead to data loss in the database.
## Functionality Overview
The newly introduced feature enhances the application with robust database backup and import capabilities. This feature is designed to ensure data integrity and provide a straightforward way to manage database backups. Here's how it works:

View File

@@ -2,7 +2,7 @@
## 1. Introduction
Stirling-PDF is a robust, locally hosted web-based PDF manipulation tool. This guide focuses on Docker-based development and testing, which is the recommended approach for working with the full version of Stirling-PDF.
Stirling-PDF is a robust, locally hosted, web-based PDF manipulation tool. This guide focuses on Docker-based development and testing, which is the recommended approach for working with the full version of Stirling-PDF.
## 2. Project Overview
@@ -11,7 +11,7 @@ Stirling-PDF is built using:
- Spring Boot + Thymeleaf
- PDFBox
- LibreOffice
- OcrMyPdf
- qpdf
- HTML, CSS, JavaScript
- Docker
- PDF.js
@@ -25,7 +25,7 @@ Stirling-PDF is built using:
- Docker
- Git
- Java JDK 17 or later
- Gradle 7.0 or later (Included within repo)
- Gradle 7.0 or later (Included within the repo)
### Setup Steps
@@ -38,14 +38,14 @@ Stirling-PDF is built using:
2. Install Docker and JDK17 if not already installed.
3. Install a recommended Java IDE such as Eclipse, IntelliJ or VSCode
3. Install a recommended Java IDE such as Eclipse, IntelliJ, or VSCode
4. Lombok Setup
Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, don't support Lombok out of the box. To set up Lombok in your development environment:
Visit the [Lombok website](https://projectlombok.org/setup/) for installation instructions specific to your IDE.
5. Add environment variable
For local testing you should generally be testing the full 'Security' version of Stirling-PDF to do this you must add the environment flag DOCKER_ENABLE_SECURITY=true to your system and/or IDE build/run step
For local testing, you should generally be testing the full 'Security' version of Stirling-PDF. To do this, you must add the environment flag DOCKER_ENABLE_SECURITY=true to your system and/or IDE build/run step.
## 4. Project Structure
@@ -86,8 +86,8 @@ Stirling-PDF/
│ └── SPDF/
├── build.gradle # Gradle build configuration
├── Dockerfile # Main Dockerfile
├── Dockerfile-ultra-lite # Dockerfile for ultra-lite version
├── Dockerfile-fat # Dockerfile for fat version
├── Dockerfile.ultra-lite # Dockerfile for ultra-lite version
├── Dockerfile.fat # Dockerfile for fat version
├── docker-compose.yml # Docker Compose configuration
└── test.sh # Test script to deploy all docker versions and run cuke tests
```
@@ -102,7 +102,7 @@ Stirling-PDF offers several Docker versions:
### Example Docker Compose Files
Stirling-PDF provides several example Docker Compose files in the `exampleYmlFiles` directory such as :
Stirling-PDF provides several example Docker Compose files in the `exampleYmlFiles` directory, such as:
- `docker-compose-latest.yml`: Latest version without security features
- `docker-compose-latest-security.yml`: Latest version with security features enabled
@@ -114,7 +114,7 @@ These files provide pre-configured setups for different scenarios. For example,
services:
stirling-pdf:
container_name: Stirling-PDF-Security
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
deploy:
resources:
limits:
@@ -173,20 +173,20 @@ Stirling-PDF uses different Docker images for various configurations. The build
For the latest version:
```bash
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest -f ./Dockerfile .
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .
```
For the ultra-lite version:
```bash
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-ultra-lite -f ./Dockerfile-ultra-lite .
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
```
For the fat version (with security enabled):
```bash
export DOCKER_ENABLE_SECURITY=true
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-fat -f ./Dockerfile-fat .
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat .
```
Note: The `--no-cache` and `--pull` flags ensure that the build process uses the latest base images and doesn't use cached layers, which is useful for testing and ensuring reproducible builds. however to improve build times these can often be removed depending on your usecase
@@ -205,9 +205,9 @@ To run the test script:
This script performs the following actions:
1. Builds all Docker images (full, ultra-lite, fat)
2. Runs each version to ensure it starts correctly
3. Executes Cucumber tests against main version and ensures feature compatibility, in the event these tests fail your PR will not be merged
1. Builds all Docker images (full, ultra-lite, fat).
2. Runs each version to ensure it starts correctly.
3. Executes Cucumber tests against the main version and ensures feature compatibility. In the event these tests fail, your PR will not be merged.
Note: The `test.sh` script will run automatically when you raise a PR. However, it's recommended to run it locally first to save resources and catch any issues early.
@@ -229,7 +229,7 @@ For quick iterations and development of Java backend, JavaScript, and UI compone
To run Stirling-PDF locally:
1. Compile and run the project using built in IDE methods or by running:
1. Compile and run the project using built-in IDE methods or by running:
```bash
./gradlew bootRun
@@ -243,7 +243,7 @@ To run Stirling-PDF locally:
Important notes:
- Local testing doesn't include features that depend on external tools like OCRmyPDF, LibreOffice, or Python scripts.
- Local testing doesn't include features that depend on external tools like qpdf, LibreOffice, or Python scripts.
- There are currently no automated unit tests. All testing is done manually through the UI or API calls. (You are welcome to add JUnits!)
- Always verify your changes in the full Docker environment before submitting pull requests, as some integrations and features will only work in the complete setup.
@@ -261,7 +261,7 @@ Important notes:
6. Push your changes to your fork.
7. Submit a pull request to the main repository.
8. See additional [contributing guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
8. See additional [contributing guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md).
When you raise a PR:
@@ -317,7 +317,7 @@ Remember to test your changes thoroughly to ensure they don't break any existing
### Overview of Thymeleaf
Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF to render dynamic web pages. Thymeleaf integrates heavily with Spring Boot
Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF to render dynamic web pages. Thymeleaf integrates heavily with Spring Boot.
### Thymeleaf overview
@@ -327,22 +327,24 @@ Some examples of this are:
```html
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
```
or
```html
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
```
Where it uses the th:block, th: indicating its a special thymeleaf element to be used serverside in generating the html, and block being the actual element type.
In this case we are inserting the ``navbar`` entry within the ``fragments/navbar.html`` fragment into the ``th:block`` element.
Where it uses the `th:block`, `th:` indicating it's a special Thymeleaf element to be used server-side in generating the HTML, and block being the actual element type.
In this case, we are inserting the `navbar` entry within the `fragments/navbar.html` fragment into the `th:block` element.
They can be more complex such as:
They can be more complex, such as:
```html
<th:block th:insert="~{fragments/common :: head(title=#{pageExtracter.title}, header=#{pageExtracter.header})}"></th:block>
```
Which is the same as above but passes the parameters title and header into the fragment common.html to be used in its HTML generation
Which is the same as above but passes the parameters title and header into the fragment `common.html` to be used in its HTML generation.
Thymeleaf can also be used to loop through objects or pass things from java side into html side.
Thymeleaf can also be used to loop through objects or pass things from the Java side into the HTML side.
```java
@GetMapping
@@ -352,7 +354,7 @@ Thymeleaf can also be used to loop through objects or pass things from java side
}
```
in above example if exampleData is a list of plain java objects of class Person and within it you had id, name, age etc. You can reference it like so
In the above example, if exampleData is a list of plain java objects of class Person and within it, you had id, name, age, etc. You can reference it like so
```html
<tbody>
@@ -452,7 +454,7 @@ This would generate n entries of tr for each person in exampleData
1. **Create a New Thymeleaf Template:**
- Create a new HTML file in the `src/main/resources/templates` directory.
- Use Thymeleaf attributes to dynamically generate content.
- Use `extract-page.html` as a base example for the HTML template, useful to ensure importing of the general layout, navbar and footer.
- Use `extract-page.html` as a base example for the HTML template, which is useful to ensure importing of the general layout, navbar, and footer.
```html
<!DOCTYPE html>

View File

@@ -10,6 +10,18 @@ COPY build/libs/*.jar app.jar
ARG VERSION_TAG
LABEL org.opencontainers.image.title="Stirling-PDF"
LABEL org.opencontainers.image.description="A powerful locally hosted web-based PDF manipulation tool supporting 50+ operations including merging, splitting, conversion, OCR, watermarking, and more."
LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.vendor="Stirling-Tools"
LABEL org.opencontainers.image.url="https://www.stirlingpdf.com"
LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com"
LABEL maintainer="Stirling-Tools"
LABEL org.opencontainers.image.authors="Stirling-Tools"
LABEL org.opencontainers.image.version="${VERSION_TAG}"
LABEL org.opencontainers.image.keywords="PDF, manipulation, merge, split, convert, OCR, watermark"
# Set Environment Variables
ENV DOCKER_ENABLE_SECURITY=false \
VERSION_TAG=$VERSION_TAG \
@@ -19,6 +31,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
PGID=1000 \
UMASK=022
# JDK for app
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
@@ -30,6 +43,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
tini \
bash \
curl \
qpdf \
shadow \
su-exec \
openssl \
@@ -40,7 +54,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
# pdftohtml
poppler-utils \
# OCR MY PDF (unpaper for descew and other advanced features)
ocrmypdf \
tesseract-ocr-data-eng \
# CV
py3-opencv \
@@ -57,8 +70,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
# User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
tesseract --list-langs
chown stirlingpdfuser:stirlingpdfgroup /app.jar
EXPOSE 8080/tcp

View File

@@ -1,5 +1,5 @@
# Build the application
FROM gradle:8.7-jdk17 AS build
FROM gradle:8.11-jdk17 AS build
# Set the working directory
WORKDIR /app
@@ -55,7 +55,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
# pdftohtml
poppler-utils \
# OCR MY PDF (unpaper for descew and other advanced featues)
ocrmypdf \
qpdf \
tesseract-ocr-data-eng \
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
# CV
@@ -73,8 +73,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
# User permissions
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
tesseract --list-langs
chown stirlingpdfuser:stirlingpdfgroup /app.jar
EXPOSE 8080/tcp

View File

@@ -1,5 +1,5 @@
# use alpine
FROM alpine:3.20.3
FROM alpine:3.21.0@sha256:21dc6063fd678b478f57c0e13f47560d0ea4eeba26dfc947b2a4f81f686b9f45
ARG VERSION_TAG

View File

@@ -1,4 +1,4 @@
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | OCRmyPDF | Java | Javascript | Unoconv | Ghostscript |
| Operation | PageOps | Convert | Security | Other | CLI | Python | OpenCV | LibreOffice | qpdf | Java | Javascript | Unoconv | tesseract |
| ------------------- | ------- | ------- | -------- | ----- | --- | ------ | ------ | ----------- | -------- | ---- | ---------- | ------- | ----------- |
| adjust-contrast | ✔️ | | | | | | | | | | ✔️ | | |
| auto-split-pdf | ✔️ | | | | | | | | | ✔️ | | | |
@@ -16,7 +16,7 @@
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | | | |
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
| pdf-to-img | | ✔️ | | | | ✔️ | | | | ✔️ | | | |
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | | | ✔️ |
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | | | |
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | | | |
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
| pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | | | |
@@ -34,13 +34,13 @@
| auto-rename | | | | ✔️ | | | | | | ✔️ | | | |
| change-metadata | | | | ✔️ | | | | | | ✔️ | | | |
| compare | | | | ✔️ | | | | | | | ✔️ | | |
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | ✔️ |
| compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | |
| extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
| extract-images | | | | ✔️ | | | | | | ✔️ | | | |
| flatten | | | | ✔️ | | | | | | | ✔️ | | |
| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | | | |
| ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | |
| ocr-pdf | | | | ✔️ | ✔️ | | | | | | | | |
| remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | |
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | | | ✔️ |
| repair | | | | ✔️ | ✔️ | | | ✔️ | | | | | |
| show-javascript | | | | ✔️ | | | | | | | ✔️ | | |
| sign | | | | ✔️ | | | | | | | ✔️ | | |

View File

@@ -8,7 +8,7 @@ The paths have changed for the tessdata locations on new Docker images. Please u
## How does the OCR Work
Stirling-PDF uses [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF), which in turn uses Tesseract for its text recognition. All credit goes to them for this awesome work!
Stirling-PDF uses Tesseract for its text recognition. All credit goes to them for this awesome work!
## Language Packs
@@ -52,8 +52,6 @@ Add the following to your existing Docker run command:
### Non-Docker Setup
If you are not using Docker, you need to install the OCR components, including the `ocrmypdf` app. You can see the [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html).
For Debian-based systems, install languages with this command:
```bash
@@ -83,8 +81,7 @@ rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g'
For Windows:
Ensure ocrmypdf in installed with
``pip install ocrmypdf``
You must ensure tesseract is installed
Additional languages must be downloaded manually:
Download desired .traineddata files from tessdata or tessdata_fast
@@ -95,8 +92,9 @@ Verify installation:
``tesseract --list-langs``
You must then edit your ``/configs/settings.yml`` and change the system.tessdataDir to match the directory containing lang files
```
system:
tessdataDir: C:/Program Files/Tesseract-OCR/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
```

45
Jenkinsfile vendored
View File

@@ -1,45 +0,0 @@
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'chmod 755 gradlew'
sh './gradlew build'
}
}
stage('Docker Build') {
steps {
script {
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
def image = "frooodle/s-pdf:$appVersion"
sh "docker build -t $image ."
}
}
}
stage('Docker Push') {
steps {
script {
def appVersion = sh(returnStdout: true, script: './gradlew printVersion -q').trim()
def image = "frooodle/s-pdf:$appVersion"
withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) {
sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN"
sh "docker push $image"
}
}
}
}
stage('Helm Push') {
steps {
script {
//TODO: Read chartVersion from Chart.yaml
def chartVersion = '1.0.0'
withCredentials([string(credentialsId: 'docker_hub_access_token', variable: 'DOCKER_HUB_ACCESS_TOKEN')]) {
sh "docker login --username frooodle --password $DOCKER_HUB_ACCESS_TOKEN"
sh "helm package chart/stirling-pdf"
sh "helm push stirling-pdf-chart-1.0.0.tgz oci://registry-1.docker.io/frooodle"
}
}
}
}
}
}

View File

@@ -13,7 +13,7 @@ You could theoretically use a Distrobox/Toolbox if your distribution has old or
Install the following software, if not already installed:
- Java 17 or later (21 recommended)
- Gradle 7.0 or later (included within repo so not needed on server)
- Gradle 7.0 or later (included within the repo, so not needed on the server)
- Git
- Python 3.8 (with pip)
- Make
@@ -32,7 +32,7 @@ sudo apt-get update
sudo apt-get install -y git automake autoconf libtool libleptonica-dev pkg-config zlib1g-dev make g++ openjdk-21-jdk python3 python3-pip
```
For Fedora-based systems use this command:
For Fedora-based systems, use this command:
```bash
sudo dnf install -y git automake autoconf libtool leptonica-devel pkg-config zlib-devel make gcc-c++ java-21-openjdk python3 python3-pip
@@ -68,7 +68,7 @@ nix-env -iA nixpkgs.jbig2enc
### Step 3: Install Additional Software
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and OpenCV for pattern recognition functionality.
Next, we need to install LibreOffice for conversions, qpdf for OCR, and OpenCV for pattern recognition functionality.
Install the following software:
@@ -81,27 +81,27 @@ Install the following software:
- unoconv
- pngquant
- unpaper
- ocrmypdf
- qpdf
- opencv-python-headless
For Debian-based systems, you can use the following command:
```bash
sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf
sudo apt-get install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper qpdf
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint --break-system-packages
```
For Fedora:
```bash
sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper ocrmypdf
sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpaper qpdf
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
```
For Nix:
```bash
nix-env -iA nixpkgs.unpaper nixpkgs.libreoffice nixpkgs.ocrmypdf nixpkgs.poppler_utils
nix-env -iA nixpkgs.unpaper nixpkgs.libreoffice nixpkgs.qpdf nixpkgs.poppler_utils
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
```
@@ -146,7 +146,6 @@ The easiest method is to use the language packs provided by your repositories. S
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
3. Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info.
**IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.
@@ -233,7 +232,7 @@ Terminal=true;
EOF
```
Note: Currently the app will run in the background until manually closed.
Note: Currently, the app will run in the background until it is manually closed.
### Optional: Changing the Host and Port of the Application
@@ -252,7 +251,7 @@ server:
### Optional: Run Stirling-PDF as a Service (requires root)
First create a `.env` file, where you can store environment variables:
First, create a `.env` file, where you can store environment variables:
```bash
touch /opt/Stirling-PDF/.env
@@ -266,7 +265,7 @@ Create a new file where we store our service settings and open it with the nano
nano /etc/systemd/system/stirlingpdf.service
```
Paste this content, make sure to update the filename of the jar file. Press `Ctrl+S` and `Ctrl+X` to save and exit the nano editor:
Paste this content, and make sure to update the filename of the jar file. Press `Ctrl+S` and `Ctrl+X` to save and exit the nano editor:
```ini
[Unit]

132
README.md
View File

@@ -2,23 +2,22 @@
<h1 align="center">Stirling-PDF</h1>
[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf)
[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/Cn8pWhQRxZ)
[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/HYmhKj45pU)
[![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/)
[![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf)
<a href="https://www.producthunt.com/posts/stirling-pdf?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-stirling&#0045;pdf" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=641239&theme=light" alt="Stirling&#0032;PDF - Open&#0032;source&#0032;locally&#0032;hosted&#0032;web&#0032;PDF&#0032;editor | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
[Stirling-PDF](https://www.stirlingpdf.com) is a robust, locally hosted web-based PDF manipulation tool using Docker. It enables you to carry out various operations on PDF files, including splitting, merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application has evolved to encompass a comprehensive set of features, addressing all your PDF requirements.
Stirling-PDF does not initiate any outbound calls for record-keeping or tracking purposes.
All files and PDFs exist either exclusively on the client side, reside in server memory only during task execution, or temporarily reside in a file solely for the execution of the task. Any file downloaded by the user will have been deleted from the server by that point.
![stirling-home](images/stirling-home.jpg)
## Features
- Enterprise features like SSO Check [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
- Enterprise features like SSO Check [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
- Dark mode support
- Custom download options
- Parallel file processing and downloads
@@ -27,12 +26,11 @@ All files and PDFs exist either exclusively on the client side, reside in server
- Optional Login and Authentication support (see [here](https://github.com/Stirling-Tools/Stirling-PDF/tree/main#login-authentication) for documentation)
- Database Backup and Import (see [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DATABASE.md) for documentation)
## PDF Features
### Page Operations
- View and modify PDFs - View multi-page PDFs with custom viewing, sorting, and searching. Plus on-page edit features like annotate, draw, and adding text and images. (Using PDF.js with Joxit and Liberation fonts)
- View and modify PDFs - View multi-page PDFs with custom viewing, sorting, and searching. Plus, on-page edit features like annotating, drawing, and adding text and images. (Using PDF.js with Joxit and Liberation fonts)
- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages
- Merge multiple PDFs into a single resultant file
- Split PDFs into multiple files at specified page numbers or extract all pages as individual files
@@ -43,11 +41,11 @@ All files and PDFs exist either exclusively on the client side, reside in server
- Scale page contents size by set percentage
- Adjust contrast
- Crop PDF
- Auto split PDF (with physically scanned page dividers)
- Auto-split PDF (with physically scanned page dividers)
- Extract page(s)
- Convert PDF to a single page
- Overlay PDFs on top of each other
- PDF to single page
- PDF to a single page
- Split PDF by sections
### Conversion Operations
@@ -56,7 +54,7 @@ All files and PDFs exist either exclusively on the client side, reside in server
- Convert any common file to PDF (using LibreOffice)
- Convert PDF to Word/PowerPoint/others (using LibreOffice)
- Convert HTML to PDF
- Convert PDF to xml
- Convert PDF to XML
- Convert PDF to CSV
- URL to PDF
- Markdown to PDF
@@ -78,15 +76,15 @@ All files and PDFs exist either exclusively on the client side, reside in server
- Detect and remove blank pages
- Compare two PDFs and show differences in text
- Add images to PDFs
- Compress PDFs to decrease their filesize (using OCRMyPDF)
- Compress PDFs to decrease their filesize (using qpdf)
- Extract images from PDF
- Remove images from PDF
- Extract images from scans
- Remove annotations
- Add page numbers
- Auto rename file by detecting PDF header text
- OCR on PDF (using OCRMyPDF)
- PDF/A conversion (using OCRMyPDF)
- Auto-rename files by detecting PDF header text
- OCR on PDF (using Tesseract OCR)
- PDF/A conversion (using LibreOffice)
- Edit metadata
- Flatten PDFs
- Get all information on a PDF to view or export as JSON
@@ -101,7 +99,7 @@ A demo of the app is available [here](https://stirlingpdf.io).
- Spring Boot + Thymeleaf
- [PDFBox](https://github.com/apache/pdfbox/tree/trunk)
- [LibreOffice](https://www.libreoffice.org/discover/libreoffice/) for advanced conversions
- [OcrMyPdf](https://github.com/ocrmypdf/OCRmyPDF)
- [qpdf](https://github.com/qpdf/qpdf)
- HTML, CSS, JavaScript
- Docker
- [PDF.js](https://github.com/mozilla/pdf.js)
@@ -120,13 +118,13 @@ Please view the [LocalRunGuide](https://github.com/Stirling-Tools/Stirling-PDF/b
### Docker / Podman
> [!NOTE]
> <https://hub.docker.com/r/frooodle/s-pdf>
> <https://hub.docker.com/r/stirlingtools/stirling-pdf>
Stirling-PDF has three different versions: a full version, an ultra-lite version, and a 'fat' version. Depending on the types of features you use, you may want a smaller image to save on space. To see what the different versions offer, please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md). For people that don't mind space optimization, just use the latest tag.
Stirling-PDF has three different versions: a full version, an ultra-lite version, and a 'fat' version. Depending on the types of features you use, you may want a smaller image to save on space. To see what the different versions offer, please look at our [version mapping](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Version-groups.md). For people who don't mind space optimization, just use the latest tag.
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest?label=Stirling-PDF%20Full)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/frooodle/s-pdf/latest-fat?label=Stirling-PDF%20Fat)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-pdf/latest?label=Stirling-PDF%20Full)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-pdf/latest-ultra-lite?label=Stirling-PDF%20Ultra-Lite)
![Docker Image Size (tag)](https://img.shields.io/docker/image-size/stirlingtools/stirling-pdf/latest-fat?label=Stirling-PDF%20Fat)
Please note in the examples below, you may need to change the volume paths as needed, e.g., `./extraConfigs:/configs` to `/opt/stirlingpdf/extraConfigs:/configs`.
@@ -144,7 +142,7 @@ docker run -d \
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
-e LANGS=en_GB \
--name stirling-pdf \
frooodle/s-pdf:latest
stirlingtools/stirling-pdf:latest
```
### Docker Compose
@@ -153,7 +151,7 @@ docker run -d \
version: '3.3'
services:
stirling-pdf:
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
ports:
- '8080:8080'
volumes:
@@ -179,53 +177,55 @@ Please view the [HowToUseOCR.md](https://github.com/Stirling-Tools/Stirling-PDF/
## Reuse Stored Files
Certain functionality like `Sign` supports pre-saved files stored at `/customFiles/signatures/`. Image files placed within here will be accessible to be used via the web UI. Currently, this supports two folder types:
Certain functionality like `Sign` supports pre-saved files stored at `/customFiles/signatures/`. Image files placed here will be accessible via the web UI. Currently, this supports two folder types:
- `/customFiles/signatures/ALL_USERS`: Accessible to all users, useful for organizations where many users use the same files or for users not using authentication
- `/customFiles/signatures/{username}`: Such as `/customFiles/signatures/froodle`, accessible only to the `froodle` username, private for all others
## Supported Languages
Stirling-PDF currently supports 36 languages!
Stirling-PDF currently supports 38 languages!
| Language | Progress |
| -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![98%](https://geps.dev/progress/98) |
| Basque (Euskara) (eu_ES) | ![55%](https://geps.dev/progress/55) |
| Bulgarian (Български) (bg_BG) | ![97%](https://geps.dev/progress/97) |
| Catalan (Català) (ca_CA) | ![90%](https://geps.dev/progress/90) |
| Croatian (Hrvatski) (hr_HR) | ![98%](https://geps.dev/progress/98) |
| Czech (Česky) (cs_CZ) | ![98%](https://geps.dev/progress/98) |
| Danish (Dansk) (da_DK) | ![97%](https://geps.dev/progress/97) |
| Dutch (Nederlands) (nl_NL) | ![96%](https://geps.dev/progress/96) |
| Arabic (العربية) (ar_AR) | ![94%](https://geps.dev/progress/94) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![92%](https://geps.dev/progress/92) |
| Basque (Euskara) (eu_ES) | ![53%](https://geps.dev/progress/53) |
| Bulgarian (Български) (bg_BG) | ![89%](https://geps.dev/progress/89) |
| Catalan (Català) (ca_CA) | ![84%](https://geps.dev/progress/84) |
| Croatian (Hrvatski) (hr_HR) | ![91%](https://geps.dev/progress/91) |
| Czech (Česky) (cs_CZ) | ![90%](https://geps.dev/progress/90) |
| Danish (Dansk) (da_DK) | ![89%](https://geps.dev/progress/89) |
| Dutch (Nederlands) (nl_NL) | ![89%](https://geps.dev/progress/89) |
| 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) | ![97%](https://geps.dev/progress/97) |
| German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) |
| Greek (Ελληνικά) (el_GR) | ![98%](https://geps.dev/progress/98) |
| Hindi (हिंदी) (hi_IN) | ![95%](https://geps.dev/progress/95) |
| Hungarian (Magyar) (hu_HU) | ![98%](https://geps.dev/progress/98) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![98%](https://geps.dev/progress/98) |
| Irish (Gaeilge) (ga_IE) | ![88%](https://geps.dev/progress/88) |
| Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) |
| Japanese (日本語) (ja_JP) | ![86%](https://geps.dev/progress/86) |
| Korean (한국어) (ko_KR) | ![96%](https://geps.dev/progress/96) |
| Norwegian (Norsk) (no_NB) | ![88%](https://geps.dev/progress/88) |
| Polish (Polski) (pl_PL) | ![97%](https://geps.dev/progress/97) |
| Portuguese (Português) (pt_PT) | ![98%](https://geps.dev/progress/98) |
| French (Français) (fr_FR) | ![92%](https://geps.dev/progress/92) |
| German (Deutsch) (de_DE) | ![100%](https://geps.dev/progress/100) |
| Greek (Ελληνικά) (el_GR) | ![90%](https://geps.dev/progress/90) |
| Hindi (हिंदी) (hi_IN) | ![88%](https://geps.dev/progress/88) |
| Hungarian (Magyar) (hu_HU) | ![91%](https://geps.dev/progress/91) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![90%](https://geps.dev/progress/90) |
| Irish (Gaeilge) (ga_IE) | ![82%](https://geps.dev/progress/82) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![93%](https://geps.dev/progress/93) |
| Korean (한국어) (ko_KR) | ![89%](https://geps.dev/progress/89) |
| Norwegian (Norsk) (no_NB) | ![82%](https://geps.dev/progress/82) |
| Persian (فارسی) (fa_IR) | ![99%](https://geps.dev/progress/99) |
| Polish (Polski) (pl_PL) | ![90%](https://geps.dev/progress/90) |
| Portuguese (Português) (pt_PT) | ![90%](https://geps.dev/progress/90) |
| Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) |
| Romanian (Română) (ro_RO) | ![90%](https://geps.dev/progress/90) |
| Russian (Русский) (ru_RU) | ![97%](https://geps.dev/progress/97) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![70%](https://geps.dev/progress/70) |
| Simplified Chinese (简体中文) (zh_CN) | ![91%](https://geps.dev/progress/91) |
| Slovakian (Slovensky) (sk_SK) | ![82%](https://geps.dev/progress/82) |
| Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) |
| Swedish (Svenska) (sv_SE) | ![97%](https://geps.dev/progress/97) |
| Thai (ไทย) (th_TH) | ![96%](https://geps.dev/progress/96) |
| Traditional Chinese (繁體中文) (zh_TW) | ![98%](https://geps.dev/progress/98) |
| Turkish (Türkçe) (tr_TR) | ![92%](https://geps.dev/progress/92) |
| Ukrainian (Українська) (uk_UA) | ![80%](https://geps.dev/progress/80) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![89%](https://geps.dev/progress/89) |
| Romanian (Română) (ro_RO) | ![84%](https://geps.dev/progress/84) |
| Russian (Русский) (ru_RU) | ![90%](https://geps.dev/progress/90) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![67%](https://geps.dev/progress/67) |
| Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) |
| Slovakian (Slovensky) (sk_SK) | ![78%](https://geps.dev/progress/78) |
| Spanish (Español) (es_ES) | ![91%](https://geps.dev/progress/91) |
| Swedish (Svenska) (sv_SE) | ![90%](https://geps.dev/progress/90) |
| Thai (ไทย) (th_TH) | ![90%](https://geps.dev/progress/90) |
| Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) |
| Turkish (Türkçe) (tr_TR) | ![86%](https://geps.dev/progress/86) |
| Ukrainian (Українська) (uk_UA) | ![76%](https://geps.dev/progress/76) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![83%](https://geps.dev/progress/83) |
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
@@ -233,12 +233,12 @@ Please see our [Contributing Guide](CONTRIBUTING.md).
## Stirling PDF Enterprise
Stirling PDF offers a Enterprise edition of its software, This is the same great software but with added features and comforts
Stirling PDF offers an Enterprise edition of its software. This is the same great software but with added features and comforts.
### Whats included
### What's included
- Prioritised Support tickets via support@stirlingpdf.com to reach directly to Stirling-PDF team for support and 1:1 meetings where applicable (Provided they come from same email domain registered with us)
- Prioritised Enhancements to Stirling-PDF where applicable
- Prioritized Support tickets via support@stirlingpdf.com to reach directly to Stirling-PDF team for support and 1:1 meetings where applicable (Provided they come from the same email domain registered with us)
- Prioritised Enhancements to Stirling-PDF where applicable
- Base SSO support
- Advanced SSO such as automated login handling (Coming very soon)
- SAML SSO (Coming very soon)
@@ -246,7 +246,7 @@ Stirling PDF offers a Enterprise edition of its software, This is the same great
- Advanced user configurations (Coming soon)
- Plus other exciting features to come
Check out of [docs](https://docs.stirlingpdf.com/Enterprise%20Edition) on it or our official [website](https://www.stirlingpdf.com)
Check out our [docs](https://docs.stirlingpdf.com/Enterprise%20Edition) on it or our official [website](https://www.stirlingpdf.com)
## Customization
@@ -364,8 +364,6 @@ AutomaticallyGenerated:
There is an additional config file `/configs/custom_settings.yml` where users familiar with Java and Spring `application.properties` can input their own settings on top of Stirling-PDF's existing ones.
### Extra Notes
- **Endpoints**: Currently, the `ENDPOINTS_TO_REMOVE` and `GROUPS_TO_REMOVE` endpoints can include comma-separated lists of endpoints and groups to disable. For example, `ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages` would disable both image-to-pdf and remove pages, while `GROUPS_TO_REMOVE=LibreOffice` would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md).
@@ -400,9 +398,9 @@ When you log in to Stirling-PDF, you will be redirected to the `/login` page to
To access your account settings, go to Account Settings in the settings cog menu (top right in the navbar). This Account Settings menu is also where you find your API key.
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here, you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
For API usage, you must provide a header with `X-API-Key` and the associated API key for that user.
For API usage, you must provide a header with `X-API-KEY` and the associated API key for that user.
## FAQ
@@ -416,9 +414,9 @@ For API usage, you must provide a header with `X-API-Key` and the associated API
- Multi-page layout (stitch PDF pages together) support x rows y columns and custom page sizing
- Fill forms manually or automatically
### Q2: Why is my application downloading .htm files? Why am i getting HTTP error 413?
### Q2: Why is my application downloading .htm files? Why am I getting HTTP error 413?
This is an issue commonly caused by your NGINX configuration. The default file upload size for NGINX is 1MB. You need to add the following in your Nginx sites-available file: `client_max_body_size SIZE;` (where "SIZE" is 50M for example for 50MB files).
This is an issue commonly caused by your NGINX configuration. The default file upload size for NGINX is 1MB. You need to add the following in your Nginx sites-available file: `client_max_body_size SIZE;` (where "SIZE" is 50M, for example, for 50MB files).
### Q3: Why is my download timing out?

63
SECURITY.md Normal file
View File

@@ -0,0 +1,63 @@
# Security Policy
## Reporting a Vulnerability
The Stirling-PDF team takes security vulnerabilities seriously. We appreciate your efforts to responsibly disclose your findings.
### How to Report
You can report security vulnerabilities through two channels:
1. **GitHub Security Advisory**:
- Navigate to the [Security tab](https://github.com/Stirling-Tools/Stirling-PDF/security) in our repository
- Click on "Report a vulnerability"
- Provide a detailed description of the vulnerability
2. **Direct Email**:
- Send your report to security@stirlingpdf.com
- Please include as much information as possible about the vulnerability
### What to Include
When reporting a vulnerability, please provide:
- A clear description of the vulnerability
- Steps to reproduce the issue
- Any potential impact
- If possible, suggestions for addressing the vulnerability
- Your contact information for follow-up questions
### Response Time
We aim to acknowledge receipt of your vulnerability report within 48 hours
### Process
1. Submit your report through one of the channels above
2. Receive an acknowledgment from our team
3. Our team will investigate and validate the issue
4. We will work on a fix and keep you updated on our progress
5. Once resolved, we will publish the fix and acknowledge your contribution (if desired)
### Bug Bounty
At this time, we do not offer a bug bounty program. However, we greatly appreciate your efforts in making Stirling-PDF more secure and will acknowledge your contribution in our release notes (unless you prefer to remain anonymous).
## Supported Versions
Only the latest version of Stirling-PDF is supported for security updates. We do not backport security fixes to older versions.
| Version | Supported |
| ------- | ------------------ |
| Latest | :white_check_mark: |
| Older | :x: |
**Please note:** Before reporting a security issue, ensure you are using the latest version of Stirling-PDF. Security reports for older versions will not be accepted.
## Security Best Practices
When deploying Stirling-PDF:
1. Always use the latest version
2. Follow our deployment guidelines
3. Regularly check for and apply updates

View File

@@ -1,14 +1,14 @@
|All versions in a Docker environment can download Calibre as a optional extra at runtime to support `book-to-pdf` and `pdf-to-book` using parameter ``INSTALL_BOOK_AND_ADVANCED_HTML_OPS``.
All versions in a Docker environment can download Calibre as a optional extra at runtime to support `book-to-pdf` and `pdf-to-book` using parameter ``INSTALL_BOOK_AND_ADVANCED_HTML_OPS``.
The 'Fat' container contains all those found in 'Full' with security jar along with this Calibre install.
| Technology | Ultra-Lite | Full |
| ---------- | :--------: | :---: |
| Java | ✔️ | ✔️ |
| JavaScript | ✔️ | ✔️ |
| Java | ✔️ | ✔️ |
| JavaScript | ✔️ | ✔️ |
| Libre | | ✔️ |
| Python | | ✔️ |
| OpenCV | | ✔️ |
| OCRmyPDF | | ✔️ |
| qpdf | | ✔️ |
| Operation | Ultra-Lite | Full |
| ---------------------- | ---------- | ---- |
@@ -54,15 +54,15 @@ The 'Fat' container contains all those found in 'Full' with security jar along w
| ocr-pdf | | ✔️ |
| pdf-to-pdfa | | ✔️ |
| remove-blanks | | ✔️ |
pdf-to-text | ✔️ | ✔️
pdf-to-html | | ✔️
pdf-to-word | | ✔️
pdf-to-presentation | | ✔️
pdf-to-xml | | ✔️
remove-annotations | ✔️ | ✔️
remove-cert-sign | ✔️ | ✔️
remove-image-pdf | ✔️ | ✔️
file-to-pdf | | ✔️
html-to-pdf | | ✔️
url-to-pdf | | ✔️
repair | | ✔️
| pdf-to-text | ✔️ | ✔️ |
| pdf-to-html | | ✔️ |
| pdf-to-word | | ✔️ |
| pdf-to-presentation | | ✔️ |
| pdf-to-xml | | ✔️ |
| remove-annotations | ✔️ | ✔️ |
| remove-cert-sign | ✔️ | ✔️ |
| remove-image-pdf | ✔️ | ✔️ |
| file-to-pdf | | ✔️ |
| html-to-pdf | | ✔️ |
| url-to-pdf | | ✔️ |
| repair | | ✔️ |

View File

@@ -1,28 +1,34 @@
plugins {
id "java"
id "org.springframework.boot" version "3.3.5"
id "io.spring.dependency-management" version "1.1.6"
id "org.springframework.boot" version "3.4.1"
id "io.spring.dependency-management" version "1.1.7"
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
id "io.swagger.swaggerhub" version "1.3.2"
id "edu.sc.seis.launch4j" version "3.0.6"
id "com.diffplug.spotless" version "6.25.0"
id "com.github.jk1.dependency-license-report" version "2.9"
//id "nebula.lint" version "19.0.3"
id("org.panteleyev.jpackageplugin") version "1.6.0"
}
import com.github.jk1.license.render.*
ext {
springBootVersion = "3.3.5"
springBootVersion = "3.4.1"
pdfboxVersion = "3.0.3"
logbackVersion = "1.5.7"
imageioVersion = "3.12.0"
lombokVersion = "1.18.34"
bouncycastleVersion = "1.78.1"
lombokVersion = "1.18.36"
bouncycastleVersion = "1.79"
springSecuritySamlVersion = "6.4.2"
openSamlVersion = "4.3.2"
}
group = "stirling.software"
version = "0.32.0"
version = "0.36.5"
java {
// 17 is lowest but we support and recommend 21
@@ -32,10 +38,9 @@ java {
repositories {
mavenCentral()
maven { url "https://jitpack.io" }
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
maven {
url 'https://build.shibboleth.net/maven/releases'
}
maven { url "https://build.shibboleth.net/maven/releases" }
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
}
licenseReport {
@@ -59,6 +64,12 @@ sourceSets {
exclude "stirling/software/SPDF/model/User.java"
exclude "stirling/software/SPDF/repository/**"
}
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
exclude "stirling/software/SPDF/UI/impl/**"
}
}
}
}
@@ -69,16 +80,153 @@ openApi {
outputFileName = "SwaggerDoc.json"
}
//0.11.5 to 2024.11.5
def getMacVersion(String version) {
def currentYear = java.time.Year.now().getValue()
def versionParts = version.split("\\.", 2)
return "${currentYear}.${versionParts.length > 1 ? versionParts[1] : versionParts[0]}"
}
jpackage {
input = "build/libs"
appName = "Stirling-PDF"
appVersion = project.version
vendor = "Stirling-Software"
appDescription = "Stirling PDF - Your Local PDF Editor"
mainJar = "Stirling-PDF-${project.version}.jar"
mainClass = "org.springframework.boot.loader.launch.JarLauncher"
icon = "src/main/resources/static/favicon.ico"
// JVM Options
javaOptions = [
"-DBROWSER_OPEN=true",
"-DSTIRLING_PDF_DESKTOP_UI=true",
"-Djava.awt.headless=false",
"-Dapple.awt.UIElement=true",
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.desktop/java.awt.event=ALL-UNNAMED",
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED"
]
verbose = true
destination = "${projectDir}/build/jpackage"
// Windows-specific configuration
windows {
launcherAsService = false
appVersion = project.version
winConsole = false
winDirChooser = true
winMenu = true
winShortcut = true
winPerUserInstall = true
winMenuGroup = "Stirling Software"
winUpgradeUuid = "2a43ed0c-b8c2-40cf-89e1-751129b87641" // Unique identifier for updates
winHelpUrl = "https://github.com/Stirling-Tools/Stirling-PDF"
winUpdateUrl = "https://github.com/Stirling-Tools/Stirling-PDF/releases"
type = "exe"
installDir = "C:/Program Files/Stirling-PDF"
}
// macOS-specific configuration
mac {
appVersion = getMacVersion(project.version.toString())
icon = "src/main/resources/static/favicon.icns"
type = "dmg"
macPackageIdentifier = "com.stirling.software.pdf"
macPackageName = "Stirling-PDF"
macAppCategory = "public.app-category.productivity"
macSign = false // Enable signing
macAppStore = false // Not targeting App Store initially
//installDir = "Applications"
// Add license and other documentation to DMG
/*macDmgContent = [
"README.md",
"LICENSE",
"CHANGELOG.md"
]*/
// Enable Mac-specific entitlements
//macEntitlements = "entitlements.plist" // You'll need to create this file
}
// Linux-specific configuration
linux {
appVersion = project.version
icon = "src/main/resources/static/favicon.png"
type = "deb" // Can also use "rpm" for Red Hat-based systems
// Debian package configuration
//linuxPackageName = "stirlingpdf"
linuxDebMaintainer = "support@stirlingpdf.com"
linuxMenuGroup = "Office;PDF;Productivity"
linuxAppCategory = "Office"
linuxAppRelease = "1"
linuxPackageDeps = true
installDir = "/opt/Stirling-PDF"
// RPM-specific settings
//linuxRpmLicenseType = "MIT"
}
// Common additional options
//jLinkOptions = [
// "--strip-debug",
// "--compress=2",
// "--no-header-files",
// "--no-man-pages"
//]
// Add any additional modules required
/*addModules = [
"java.base",
"java.desktop",
"java.logging",
"java.sql",
"java.xml",
"jdk.crypto.ec"
]*/
// Add copyright and license information
copyright = "Copyright © 2024 Stirling Software"
licenseFile = "LICENSE"
}
launch4j {
icon = "${projectDir}/src/main/resources/static/favicon.ico"
outfile="Stirling-PDF.exe"
headerType="console"
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
headerType = "gui"
} else {
headerType = "console"
}
jarTask = tasks.bootJar
errTitle="Encountered error, Do you have Java 21?"
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
variables=["BROWSER_OPEN=true"]
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
} else {
variables=["BROWSER_OPEN=true"]
}
jreMinVersion="17"
mutexName="Stirling-PDF"
@@ -95,7 +243,7 @@ spotless {
java {
target project.fileTree('src/main/java')
googleJavaFormat("1.22.0").aosp().reorderImports(false)
googleJavaFormat("1.25.2").aosp().reorderImports(false)
importOrder("java", "javax", "org", "com", "net", "io")
toggleOffOn()
@@ -118,10 +266,17 @@ configurations.all {
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
dependencies {
//security updates
implementation "org.springframework:spring-webmvc:6.1.14"
implementation("io.github.pixee:java-security-toolkit:1.2.0")
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
implementation "me.friwi:jcefmaven:127.3.1"
implementation "org.openjfx:javafx-controls:21"
implementation "org.openjfx:javafx-swing:21"
}
//security updates
implementation "org.springframework:spring-webmvc:6.2.1"
implementation("io.github.pixee:java-security-toolkit:1.2.1")
// implementation "org.yaml:snakeyaml:2.2"
implementation 'com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4'
@@ -137,22 +292,22 @@ dependencies {
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE"
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.3.4'
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
//2.2.x requires rebuild of DB file.. need migration path
runtimeOnly "com.h2database:h2:2.1.214"
// implementation "com.h2database:h2:2.2.224"
constraints {
implementation "org.opensaml:opensaml-core"
implementation "org.opensaml:opensaml-saml-api"
implementation "org.opensaml:opensaml-saml-impl"
}
implementation "org.springframework.security:spring-security-saml2-service-provider"
implementation "org.springframework.session:spring-session-core:$springBootVersion"
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
// Don't upgrade h2database
runtimeOnly "com.h2database:h2:2.3.232"
constraints {
implementation "org.opensaml:opensaml-core:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-api:$openSamlVersion"
implementation "org.opensaml:opensaml-saml-impl:$openSamlVersion"
}
implementation "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
// implementation 'org.springframework.security:spring-security-core:$springSecuritySamlVersion'
implementation 'com.coveo:saml-client:5.0.0'
@@ -184,7 +339,7 @@ dependencies {
// Image metadata extractor
implementation "com.drewnoakes:metadata-extractor:2.19.0"
implementation "commons-io:commons-io:2.17.0"
implementation "commons-io:commons-io:2.18.0"
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
//general PDF
@@ -201,12 +356,19 @@ dependencies {
exclude group: "commons-logging", module: "commons-logging"
}
// https://mvnrepository.com/artifact/technology.tabula/tabula
implementation ('technology.tabula:tabula:1.0.5') {
exclude group: "org.slf4j", module: "slf4j-simple"
exclude group: "org.bouncycastle", module: "bcprov-jdk15on"
exclude group: "com.google.code.gson", module: "gson"
}
implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4'
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.13.6"
implementation "io.micrometer:micrometer-core:1.14.2"
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0"
@@ -259,7 +421,14 @@ jar {
tasks.named("test") {
useJUnitPlatform()
}
task printVersion {
println project.version
doLast {
println project.version
}
}
task printMacVersion {
doLast {
println getMacVersion(project.version.toString())
}
}

View File

@@ -48,24 +48,6 @@ Feature: API Validation
And the response status code should be 200
@ocr @negative
Scenario: Process PDF with text and OCR with type normal
Given I generate a PDF file as "fileInput"
And the pdf contains 3 pages with random text
And the request data includes
| parameter | value |
| languages | eng |
| sidecar | false |
| deskew | true |
| clean | true |
| cleanFinal | true |
| ocrType | Normal |
| ocrRenderType | hocr |
| removeImagesAfter| false |
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
Then the response status code should be 500
@ocr @positive
Scenario: Process PDF with OCR
Given I generate a PDF file as "fileInput"
@@ -83,26 +65,6 @@ Feature: API Validation
Then the response content type should be "application/pdf"
And the response file should have size greater than 0
And the response status code should be 200
@ocr @positive
Scenario: Process PDF with OCR with sidecar
Given I generate a PDF file as "fileInput"
And the request data includes
| parameter | value |
| languages | eng |
| sidecar | true |
| deskew | true |
| clean | true |
| cleanFinal | true |
| ocrType | Force |
| ocrRenderType | hocr |
| removeImagesAfter| false |
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
Then the response content type should be "application/octet-stream"
And the response file should have extension ".zip"
And the response ZIP should contain 2 files
And the response file should have size greater than 0
And the response status code should be 200
@libre @positive
@@ -145,7 +107,7 @@ Feature: API Validation
And the response file should have extension ".pdf"
And the response file should have size greater than 100
@compress @ghostscript @positive
@compress @qpdf @positive
Scenario: Compress
Given I use an example file at "exampleFiles/ghost3.pdf" as parameter "fileInput"
And the request data includes
@@ -156,7 +118,7 @@ Feature: API Validation
And the response file should have extension ".pdf"
And the response file should have size greater than 100
@compress @ghostscript @positive
@compress @qpdf @positive
Scenario: Compress
Given I use an example file at "exampleFiles/ghost2.pdf" as parameter "fileInput"
And the request data includes
@@ -169,7 +131,7 @@ Feature: API Validation
And the response file should have size greater than 100
@compress @ghostscript @positive
@compress @qpdf @positive
Scenario: Compress
Given I use an example file at "exampleFiles/ghost1.pdf" as parameter "fileInput"
And the request data includes

View File

@@ -15,6 +15,10 @@ import shutil
import re
from PIL import Image, ImageDraw
API_HEADERS = {
'X-API-KEY': '123456789'
}
#########
# GIVEN #
#########
@@ -227,7 +231,7 @@ def save_generated_pdf(context, filename):
def step_send_get_request(context, endpoint):
base_url = "http://localhost:8080"
full_url = f"{base_url}{endpoint}"
response = requests.get(full_url)
response = requests.get(full_url, headers=API_HEADERS)
context.response = response
@when('I send a GET request to "{endpoint}" with parameters')
@@ -235,7 +239,7 @@ def step_send_get_request_with_params(context, endpoint):
base_url = "http://localhost:8080"
params = {row['parameter']: row['value'] for row in context.table}
full_url = f"{base_url}{endpoint}"
response = requests.get(full_url, params=params)
response = requests.get(full_url, params=params, headers=API_HEADERS)
context.response = response
@when('I send the API request to the endpoint "{endpoint}"')
@@ -256,7 +260,7 @@ def step_send_api_request(context, endpoint):
print(f"form_data {file.name} with {mime_type}")
form_data.append((key, (file.name, file, mime_type)))
response = requests.post(url, files=form_data)
response = requests.post(url, files=form_data, headers=API_HEADERS)
context.response = response
########

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security-Fat
image: frooodle/s-pdf:latest-fat
image: stirlingtools/stirling-pdf:latest-fat
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite-Security
image: frooodle/s-pdf:latest-ultra-lite
image: stirlingtools/stirling-pdf:latest-ultra-lite
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Ultra-Lite
image: frooodle/s-pdf:latest-ultra-lite
image: stirlingtools/stirling-pdf:latest-ultra-lite
deploy:
resources:
limits:

View File

@@ -1,7 +1,7 @@
services:
stirling-pdf:
container_name: Stirling-PDF
image: frooodle/s-pdf:latest
image: stirlingtools/stirling-pdf:latest
deploy:
resources:
limits:

View File

@@ -0,0 +1,34 @@
services:
stirling-pdf:
container_name: Stirling-PDF-Security-Fat
image: stirlingtools/stirling-pdf:latest-fat
deploy:
resources:
limits:
memory: 4G
healthcheck:
test: ["CMD-SHELL", "curl -f -H 'X-API-KEY: 123456789' http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
interval: 5s
timeout: 10s
retries: 16
ports:
- 8080:8080
volumes:
- /stirling/latest/data:/usr/share/tessdata:rw
- /stirling/latest/config:/configs:rw
- /stirling/latest/logs:/logs:rw
environment:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
PUID: 1002
PGID: 1002
UMASK: "022"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "true"
SECURITY_CUSTOMGLOBALAPIKEY: "123456789"
restart: on-failure:5

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -3,6 +3,11 @@ ignore = [
'language.direction',
]
[az_AZ]
ignore = [
'language.direction',
]
[bg_BG]
ignore = [
'language.direction',
@@ -32,21 +37,25 @@ ignore = [
ignore = [
'AddStampRequest.alphabet',
'AddStampRequest.position',
'home.pipeline.title'
'PDFToBook.selectText.1',
'PDFToText.tags',
'addPageNumbers.selectText.3',
'alphabet',
'certSign.name',
'fileChooser.dragAndDrop',
'home.pipeline.title',
'language.direction',
'legal.impressum',
'licenses.version',
'pipeline.title',
'pipelineOptions.pipelineHeader',
'pro',
'sponsor',
'text',
'validateSignature.cert.bits',
'validateSignature.cert.version',
'validateSignature.status',
'watermark.type.1',
'certSign.name',
]
[el_GR]
@@ -57,7 +66,6 @@ ignore = [
[es_ES]
ignore = [
'adminUserSettings.roles',
'color',
'error',
'language.direction',
'no',
@@ -69,6 +77,11 @@ ignore = [
'language.direction',
]
[fa_IR]
ignore = [
'language.direction',
]
[fr_FR]
ignore = [
'AddStampRequest.alphabet',

View File

@@ -16,10 +16,10 @@ fi
# Check if TESSERACT_LANGS environment variable is set and is not empty
if [[ -n "$TESSERACT_LANGS" ]]; then
# Convert comma-separated values to a space-separated list
LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
SPACE_SEPARATED_LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$'
# Install each language pack
for LANG in $LANGS; do
for LANG in $SPACE_SEPARATED_LANGS; do
if [[ $LANG =~ $pattern ]]; then
apk add --no-cache "tesseract-ocr-data-$LANG"
else

View File

@@ -1,8 +1,8 @@
#!/bin/bash
translation_key="pdfToPDFA.credit"
old_value="OCRmyPDF"
new_value="ghostscript"
old_value="qpdf"
new_value="liibreoffice"
for file in ../src/main/resources/messages_*.properties; do
sed -i "/^$translation_key=/s/$old_value/$new_value/" "$file"

View File

@@ -97,14 +97,14 @@ public abstract class CreateSignatureBase implements SignatureInterface {
this.privateKey = privateKey;
}
public final void setCertificateChain(final Certificate[] certificateChain) {
this.certificateChain = certificateChain;
}
public Certificate[] getCertificateChain() {
return certificateChain;
}
public final void setCertificateChain(final Certificate[] certificateChain) {
this.certificateChain = certificateChain;
}
public void setTsaUrl(String tsaUrl) {
this.tsaUrl = tsaUrl;
}
@@ -152,6 +152,10 @@ public abstract class CreateSignatureBase implements SignatureInterface {
}
}
public boolean isExternalSigning() {
return externalSigning;
}
/**
* Set if external signing scenario should be used. If {@code false}, SignatureInterface would
* be used for signing.
@@ -163,8 +167,4 @@ public abstract class CreateSignatureBase implements SignatureInterface {
public void setExternalSigning(boolean externalSigning) {
this.externalSigning = externalSigning;
}
public boolean isExternalSigning() {
return externalSigning;
}
}

View File

@@ -51,15 +51,13 @@ public class TSAClient {
private static final DigestAlgorithmIdentifierFinder ALGORITHM_OID_FINDER =
new DefaultDigestAlgorithmIdentifierFinder();
// SecureRandom.getInstanceStrong() would be better, but sometimes blocks on Linux
private static final Random RANDOM = new SecureRandom();
private final URL url;
private final String username;
private final String password;
private final MessageDigest digest;
// SecureRandom.getInstanceStrong() would be better, but sometimes blocks on Linux
private static final Random RANDOM = new SecureRandom();
/**
* @param url the URL of the TSA service
* @param username user name of TSA

View File

@@ -1,20 +1,27 @@
package stirling.software.SPDF.EE;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
@Lazy
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class EEAppConfig {
@Autowired ApplicationProperties applicationProperties;
@Autowired private LicenseKeyChecker licenseKeyChecker;
private final ApplicationProperties applicationProperties;
private final LicenseKeyChecker licenseKeyChecker;
public EEAppConfig(
ApplicationProperties applicationProperties, LicenseKeyChecker licenseKeyChecker) {
this.applicationProperties = applicationProperties;
this.licenseKeyChecker = licenseKeyChecker;
}
@Bean(name = "runningEE")
public boolean runningEnterpriseEdition() {

View File

@@ -25,9 +25,10 @@ public class LicenseKeyChecker {
KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {
this.licenseService = licenseService;
this.applicationProperties = applicationProperties;
this.checkLicense();
}
@Scheduled(fixedRate = 604800000, initialDelay = 1000) // 7 days in milliseconds
@Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds
public void checkLicensePeriodically() {
checkLicense();
}

View File

@@ -6,9 +6,6 @@ import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.pixee.security.SystemCommand;
import lombok.extern.slf4j.Slf4j;
@@ -16,23 +13,20 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LibreOfficeListener {
private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class);
private static final long ACTIVITY_TIMEOUT = 20L * 60 * 1000; // 20 minutes
private static final LibreOfficeListener INSTANCE = new LibreOfficeListener();
private static final int LISTENER_PORT = 2002;
private ExecutorService executorService;
private long lastActivityTime;
private Process process;
private LibreOfficeListener() {}
public static LibreOfficeListener getInstance() {
return INSTANCE;
}
private ExecutorService executorService;
private long lastActivityTime;
private Process process;
private LibreOfficeListener() {}
private boolean isListenerRunning() {
log.info("waiting for listener to start");
try (Socket socket = new Socket()) {
@@ -87,7 +81,7 @@ public class LibreOfficeListener {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("exception", e);
log.error("exception", e);
} // Check every 1 second
}
}

View File

@@ -8,9 +8,8 @@ import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
@@ -21,33 +20,33 @@ import org.springframework.scheduling.annotation.EnableScheduling;
import io.github.pixee.security.SystemCommand;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.UI.WebBrowser;
import stirling.software.SPDF.config.ConfigInitializer;
import stirling.software.SPDF.model.ApplicationProperties;
@SpringBootApplication
@EnableScheduling
@Slf4j
public class SPdfApplication {
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
@Autowired private Environment env;
@Autowired ApplicationProperties applicationProperties;
private static String baseUrlStatic;
private static String serverPortStatic;
private final Environment env;
private final ApplicationProperties applicationProperties;
private final WebBrowser webBrowser;
@Value("${baseUrl:http://localhost}")
private String baseUrl;
@Value("${server.port:8080}")
public void setServerPortStatic(String port) {
if ("auto".equalsIgnoreCase(port)) {
// Use Spring Boot's automatic port assignment (server.port=0)
SPdfApplication.serverPortStatic =
"0"; // This will let Spring Boot assign an available port
} else {
SPdfApplication.serverPortStatic = port;
}
public SPdfApplication(
Environment env,
ApplicationProperties applicationProperties,
@Autowired(required = false) WebBrowser webBrowser) {
this.env = env;
this.applicationProperties = applicationProperties;
this.webBrowser = webBrowser;
}
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
@@ -67,47 +66,24 @@ public class SPdfApplication {
}
}
@PostConstruct
public void init() {
baseUrlStatic = this.baseUrl;
// Check if the BROWSER_OPEN environment variable is set to true
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
if (browserOpen) {
try {
String url = baseUrl + ":" + getStaticPort();
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 (Exception e) {
logger.error("Error opening browser: {}", e.getMessage());
}
}
logger.info("Running configs {}", applicationProperties.toString());
}
public static void main(String[] args) throws IOException, InterruptedException {
SpringApplication app = new SpringApplication(SPdfApplication.class);
Properties props = new Properties();
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
System.setProperty("java.awt.headless", "false");
app.setHeadless(false);
props.put("java.awt.headless", "false");
props.put("spring.main.web-application-type", "servlet");
}
app.setAdditionalProfiles("default");
app.addInitializers(new ConfigInitializer());
Map<String, String> propertyFiles = new HashMap<>();
// External config files
if (Files.exists(Paths.get("configs/settings.yml"))) {
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
} else {
logger.warn("External configuration file 'configs/settings.yml' does not exist.");
log.warn("External configuration file 'configs/settings.yml' does not exist.");
}
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
String existingLocation =
propertyFiles.getOrDefault("spring.config.additional-location", "");
@@ -118,47 +94,96 @@ public class SPdfApplication {
"spring.config.additional-location",
existingLocation + "file:configs/custom_settings.yml");
} else {
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
log.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
}
Properties finalProps = new Properties();
if (!propertyFiles.isEmpty()) {
app.setDefaultProperties(
finalProps.putAll(
Collections.singletonMap(
"spring.config.additional-location",
propertyFiles.get("spring.config.additional-location")));
}
if (!props.isEmpty()) {
finalProps.putAll(props);
}
app.setDefaultProperties(finalProps);
app.run(args);
// Ensure directories are created
try {
Files.createDirectories(Path.of("customFiles/static/"));
Files.createDirectories(Path.of("customFiles/templates/"));
} catch (Exception e) {
logger.error("Error creating directories: {}", e.getMessage());
log.error("Error creating directories: {}", e.getMessage());
}
printStartupLogs();
}
private static void printStartupLogs() {
logger.info("Stirling-PDF Started.");
log.info("Stirling-PDF Started.");
String url = baseUrlStatic + ":" + getStaticPort();
logger.info("Navigate to {}", url);
log.info("Navigate to {}", url);
}
public static String getStaticBaseUrl() {
return baseUrlStatic;
}
public String getNonStaticBaseUrl() {
return baseUrlStatic;
}
public static String getStaticPort() {
return serverPortStatic;
}
@Value("${server.port:8080}")
public void setServerPortStatic(String port) {
if ("auto".equalsIgnoreCase(port)) {
// Use Spring Boot's automatic port assignment (server.port=0)
SPdfApplication.serverPortStatic = // This will let Spring Boot assign an available port
"0";
} else {
SPdfApplication.serverPortStatic = port;
}
}
@PostConstruct
public void init() {
baseUrlStatic = this.baseUrl;
String url = baseUrl + ":" + getStaticPort();
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();
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 (Exception e) {
log.error("Error opening browser: {}", e.getMessage());
}
}
}
log.info("Running configs {}", applicationProperties.toString());
}
@PreDestroy
public void cleanup() {
if (webBrowser != null) {
webBrowser.cleanup();
}
}
public String getNonStaticBaseUrl() {
return baseUrlStatic;
}
public String getNonStaticPort() {
return serverPortStatic;
}

View File

@@ -0,0 +1,7 @@
package stirling.software.SPDF.UI;
public interface WebBrowser {
void initWebUI(String url);
void cleanup();
}

View File

@@ -0,0 +1,354 @@
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;
@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());
// 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();
// Show the frame immediately but transparent
frame.setVisible(true);
});
} catch (Exception e) {
log.error("Error initializing JCEF browser: ", e);
cleanup();
}
});
}
private void configureCefSettings(CefAppBuilder builder) {
CefSettings settings = builder.getCefSettings();
settings.cache_path = new File("jcef-bundle").getAbsolutePath();
settings.root_cache_path = new File("jcef-bundle").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(1280, 768);
frame.setLocationRelativeTo(null);
loadIcon();
}
private void setupLoadHandler() {
client.addLoadHandler(
new CefLoadHandlerAdapter() {
@Override
public void onLoadingStateChange(
CefBrowser browser,
boolean isLoading,
boolean canGoBack,
boolean canGoForward) {
if (!isLoading && !browserInitialized) {
browserInitialized = true;
SwingUtilities.invokeLater(
() -> {
if (loadingWindow != null) {
Timer timer =
new Timer(
500,
e -> {
loadingWindow.dispose();
loadingWindow = null;
frame.dispose();
frame.setOpacity(1.0f);
frame.setUndecorated(false);
frame.pack();
frame.setSize(1280, 800);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.requestFocus();
frame.toFront();
browser.getUIComponent()
.requestFocus();
});
timer.setRepeats(false);
timer.start();
}
});
}
}
});
}
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();
}
}

View File

@@ -0,0 +1,114 @@
package stirling.software.SPDF.UI.impl;
import java.awt.*;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.swing.*;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LoadingWindow extends JDialog {
private final JProgressBar progressBar;
private final JLabel statusLabel;
private final JPanel mainPanel;
private final JLabel brandLabel;
public LoadingWindow(Frame parent, String initialUrl) {
super(parent, "Initializing Stirling-PDF", true);
// 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; // Add horizontal weight
gbc.weighty = 0.0; // Add vertical weight
// Add icon
try {
try (InputStream is = getClass().getResourceAsStream("/static/favicon.ico")) {
if (is != null) {
Image img = ImageIO.read(is);
if (img != null) {
Image scaledImg = img.getScaledInstance(48, 48, Image.SCALE_SMOOTH);
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
gbc.gridy = 0;
mainPanel.add(iconLabel, gbc);
}
}
}
} 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(400, 200);
setLocationRelativeTo(parent);
setAlwaysOnTop(true);
setProgress(0);
setStatus("Starting...");
}
public void setProgress(final int progress) {
SwingUtilities.invokeLater(
() -> {
try {
progressBar.setValue(Math.min(Math.max(progress, 0), 100));
progressBar.setString(progress + "%");
mainPanel.revalidate();
mainPanel.repaint();
} catch (Exception e) {
log.error("Error updating progress", e);
}
});
}
public void setStatus(final String status) {
log.info(status);
SwingUtilities.invokeLater(
() -> {
try {
statusLabel.setText(status != null ? status : "");
mainPanel.revalidate();
mainPanel.repaint();
} catch (Exception e) {
log.error("Error updating status", e);
}
});
}
}

View File

@@ -7,9 +7,6 @@ import java.nio.file.Paths;
import java.util.Properties;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@@ -21,15 +18,19 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.spring6.SpringTemplateEngine;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
@Lazy
@Slf4j
public class AppConfig {
private static final Logger logger = LoggerFactory.getLogger(AppConfig.class);
private final ApplicationProperties applicationProperties;
@Autowired ApplicationProperties applicationProperties;
public AppConfig(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@Bean
@ConditionalOnProperty(
@@ -61,7 +62,7 @@ public class AppConfig {
props.load(resource.getInputStream());
return props.getProperty("version");
} catch (IOException e) {
logger.error("exception", e);
log.error("exception", e);
}
return "0.0.0";
}
@@ -101,6 +102,25 @@ public class AppConfig {
return Files.exists(Paths.get("/.dockerenv"));
}
@Bean(name = "configDirMounted")
public boolean isRunningInDockerWithConfig() {
Path dockerEnv = Paths.get("/.dockerenv");
// default to true if not docker
if (!Files.exists(dockerEnv)) {
return true;
}
Path mountInfo = Paths.get("/proc/1/mountinfo");
// this should always exist, if not some unknown usecase
if (!Files.exists(mountInfo)) {
return true;
}
try {
return Files.lines(mountInfo).anyMatch(line -> line.contains(" /configs "));
} catch (IOException e) {
return false;
}
}
@Bean(name = "bookAndHtmlFormatsInstalled")
public boolean bookAndHtmlFormatsInstalled() {
String installOps = System.getProperty("INSTALL_BOOK_AND_ADVANCED_HTML_OPS");

View File

@@ -11,10 +11,16 @@ import stirling.software.SPDF.model.ApplicationProperties;
@Service
class AppUpdateService {
@Autowired private ApplicationProperties applicationProperties;
private final ApplicationProperties applicationProperties;
@Autowired(required = false)
ShowAdminInterface showAdmin;
private final ShowAdminInterface showAdmin;
public AppUpdateService(
ApplicationProperties applicationProperties,
@Autowired(required = false) ShowAdminInterface showAdmin) {
this.applicationProperties = applicationProperties;
this.showAdmin = showAdmin;
}
@Bean(name = "shouldShow")
@Scope("request")

View File

@@ -16,16 +16,15 @@ import org.simpleyaml.configuration.comments.CommentType;
import org.simpleyaml.configuration.file.YamlFile;
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ConfigInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Logger logger = LoggerFactory.getLogger(ConfigInitializer.class);
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
@@ -149,7 +148,7 @@ public class ConfigInitializer
.commentSide(settingsTemplateFile.getComment(path, CommentType.SIDE));
} else {
// Log if the key is not found in both YAML files
logger.info("Key not found in both YAML files: " + path);
log.info("Key not found in both YAML files: " + path);
}
}
}

View File

@@ -7,24 +7,23 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
@Service
@Slf4j
@DependsOn({"bookAndHtmlFormatsInstalled"})
public class EndpointConfiguration {
private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class);
private static final String REMOVE_BLANKS = "remove-blanks";
private final ApplicationProperties applicationProperties;
private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
private final ApplicationProperties applicationProperties;
private boolean bookAndHtmlFormatsInstalled;
@Autowired
@@ -43,7 +42,7 @@ public class EndpointConfiguration {
public void disableEndpoint(String endpoint) {
if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {
logger.debug("Disabling {}", endpoint);
log.debug("Disabling {}", endpoint);
endpointStatuses.put(endpoint, false);
}
}
@@ -87,7 +86,7 @@ public class EndpointConfiguration {
.collect(Collectors.toList());
if (!disabledList.isEmpty()) {
logger.info(
log.info(
"Total disabled endpoints: {}. Disabled endpoints: {}",
disabledList.size(),
String.join(", ", disabledList));
@@ -188,7 +187,7 @@ public class EndpointConfiguration {
addEndpointToGroup("OpenCV", "extract-image-scans");
// LibreOffice
addEndpointToGroup("LibreOffice", "repair");
addEndpointToGroup("qpdf", "repair");
addEndpointToGroup("LibreOffice", "file-to-pdf");
addEndpointToGroup("LibreOffice", "pdf-to-word");
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
@@ -199,10 +198,11 @@ public class EndpointConfiguration {
// Unoconv
addEndpointToGroup("Unoconv", "file-to-pdf");
// OCRmyPDF
addEndpointToGroup("OCRmyPDF", "compress-pdf");
addEndpointToGroup("OCRmyPDF", "pdf-to-pdfa");
addEndpointToGroup("OCRmyPDF", "ocr-pdf");
// qpdf
addEndpointToGroup("qpdf", "compress-pdf");
addEndpointToGroup("qpdf", "pdf-to-pdfa");
addEndpointToGroup("tesseract", "ocr-pdf");
// Java
addEndpointToGroup("Java", "merge-pdfs");
@@ -248,10 +248,10 @@ public class EndpointConfiguration {
addEndpointToGroup("Javascript", "compare");
addEndpointToGroup("Javascript", "adjust-contrast");
// Ghostscript dependent endpoints
addEndpointToGroup("Ghostscript", "compress-pdf");
addEndpointToGroup("Ghostscript", "pdf-to-pdfa");
addEndpointToGroup("Ghostscript", "repair");
// qpdf dependent endpoints
addEndpointToGroup("qpdf", "compress-pdf");
addEndpointToGroup("qpdf", "pdf-to-pdfa");
addEndpointToGroup("qpdf", "repair");
// Weasyprint dependent endpoints
addEndpointToGroup("Weasyprint", "html-to-pdf");
@@ -259,6 +259,9 @@ public class EndpointConfiguration {
// Pdftohtml dependent endpoints
addEndpointToGroup("Pdftohtml", "pdf-to-html");
// disabled for now while we resolve issues
disableEndpoint("pdf-to-pdfa");
}
private void processEnvironmentConfigs() {
@@ -283,6 +286,4 @@ public class EndpointConfiguration {
public Set<String> getEndpointsForGroup(String group) {
return endpointGroups.getOrDefault(group, new HashSet<>());
}
private static final String REMOVE_BLANKS = "remove-blanks";
}

View File

@@ -1,6 +1,5 @@
package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@@ -10,7 +9,11 @@ import jakarta.servlet.http.HttpServletResponse;
@Component
public class EndpointInterceptor implements HandlerInterceptor {
@Autowired private EndpointConfiguration endpointConfiguration;
private final EndpointConfiguration endpointConfiguration;
public EndpointInterceptor(EndpointConfiguration endpointConfiguration) {
this.endpointConfiguration = endpointConfiguration;
}
@Override
public boolean preHandle(

View File

@@ -6,7 +6,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PostConstruct;
@@ -15,7 +14,24 @@ import lombok.extern.slf4j.Slf4j;
@Configuration
@Slf4j
public class ExternalAppDepConfig {
@Autowired private EndpointConfiguration endpointConfiguration;
private final EndpointConfiguration endpointConfiguration;
private final Map<String, List<String>> commandToGroupMapping =
new HashMap<>() {
{
put("soffice", List.of("LibreOffice"));
put("weasyprint", List.of("Weasyprint"));
put("pdftohtml", List.of("Pdftohtml"));
put("unoconv", List.of("Unoconv"));
put("qpdf", List.of("qpdf"));
put("tesseract", List.of("tesseract"));
}
};
public ExternalAppDepConfig(EndpointConfiguration endpointConfiguration) {
this.endpointConfiguration = endpointConfiguration;
}
private boolean isCommandAvailable(String command) {
try {
@@ -34,18 +50,6 @@ public class ExternalAppDepConfig {
}
}
private final Map<String, List<String>> commandToGroupMapping =
new HashMap<>() {
{
put("gs", List.of("Ghostscript"));
put("soffice", List.of("LibreOffice"));
put("ocrmypdf", List.of("OCRmyPDF"));
put("weasyprint", List.of("Weasyprint"));
put("pdftohtml", List.of("Pdftohtml"));
put("unoconv", List.of("Unoconv"));
}
};
private List<String> getAffectedFeatures(String group) {
return endpointConfiguration.getEndpointsForGroup(group).stream()
.map(endpoint -> formatEndpointAsFeature(endpoint))
@@ -55,7 +59,6 @@ public class ExternalAppDepConfig {
private String formatEndpointAsFeature(String endpoint) {
// First replace common terms
String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image");
// Split into words and capitalize each word
return Arrays.stream(feature.split("\\s+"))
.map(word -> capitalizeWord(word))
@@ -76,7 +79,6 @@ public class ExternalAppDepConfig {
boolean isAvailable = isCommandAvailable(command);
if (!isAvailable) {
List<String> affectedGroups = commandToGroupMapping.get(command);
if (affectedGroups != null) {
for (String group : affectedGroups) {
List<String> affectedFeatures = getAffectedFeatures(group);
@@ -95,21 +97,18 @@ public class ExternalAppDepConfig {
@PostConstruct
public void checkDependencies() {
// Check core dependencies
checkDependencyAndDisableGroup("gs");
checkDependencyAndDisableGroup("tesseract");
checkDependencyAndDisableGroup("soffice");
checkDependencyAndDisableGroup("ocrmypdf");
checkDependencyAndDisableGroup("qpdf");
checkDependencyAndDisableGroup("weasyprint");
checkDependencyAndDisableGroup("pdftohtml");
checkDependencyAndDisableGroup("unoconv");
// Special handling for Python/OpenCV dependencies
boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python");
if (!pythonAvailable) {
List<String> pythonFeatures = getAffectedFeatures("Python");
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
endpointConfiguration.disableGroup("Python");
endpointConfiguration.disableGroup("OpenCV");
log.warn(

View File

@@ -1,11 +1,13 @@
package stirling.software.SPDF.config;
import java.io.IOException;
import java.util.Properties;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import io.micrometer.common.util.StringUtils;
@@ -20,44 +22,80 @@ import stirling.software.SPDF.utils.GeneralUtils;
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public class InitialSetup {
@Autowired private ApplicationProperties applicationProperties;
private final ApplicationProperties applicationProperties;
public InitialSetup(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@PostConstruct
public void init() throws IOException {
initUUIDKey();
initSecretKey();
initEnableCSRFSecurity();
initLegalUrls();
initSetAppVersion();
}
public void initUUIDKey() throws IOException {
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
if (!GeneralUtils.isValidUUID(uuid)) {
uuid = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
// Generating a random UUID as the secret key
uuid = UUID.randomUUID().toString();
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.UUID", uuid);
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
}
}
@PostConstruct
public void initSecretKey() throws IOException {
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
if (!GeneralUtils.isValidUUID(secretKey)) {
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
// Generating a random UUID as the secret key
secretKey = UUID.randomUUID().toString();
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.key", secretKey);
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
}
}
@PostConstruct
public void initEnableCSRFSecurity() throws IOException {
if (GeneralUtils.isVersionHigher(
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
if (!csrf) {
GeneralUtils.saveKeyToConfig("security.csrfDisabled", false, false);
GeneralUtils.saveKeyToConfig("system.enableAnalytics", "true", false);
applicationProperties.getSecurity().setCsrfDisabled(false);
}
}
}
public void initLegalUrls() throws IOException {
// Initialize Terms and Conditions
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
if (StringUtils.isEmpty(termsUrl)) {
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl);
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false);
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
}
// Initialize Privacy Policy
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
if (StringUtils.isEmpty(privacyUrl)) {
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl);
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl, false);
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
}
}
public void initSetAppVersion() throws IOException {
String appVersion = "0.0.0";
Resource resource = new ClassPathResource("version.properties");
Properties props = new Properties();
try {
props.load(resource.getInputStream());
appVersion = props.getProperty("version");
} catch (Exception e) {
}
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
}
}

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF.config;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
@@ -16,7 +15,11 @@ import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
public class LocaleConfiguration implements WebMvcConfigurer {
@Autowired ApplicationProperties applicationProperties;
private final ApplicationProperties applicationProperties;
public LocaleConfiguration(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
@@ -34,21 +37,17 @@ public class LocaleConfiguration implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();
Locale defaultLocale =
Locale.UK; // Fallback to UK locale if environment variable is not set
Locale defaultLocale = // Fallback to UK locale if environment variable is not set
Locale.UK;
if (appLocaleEnv != null && !appLocaleEnv.isEmpty()) {
Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);
String tempLanguageTag = tempLocale.toLanguageTag();
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
defaultLocale = tempLocale;
} else {
tempLocale = Locale.forLanguageTag(appLocaleEnv.replace("_", "-"));
tempLanguageTag = tempLocale.toLanguageTag();
if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {
defaultLocale = tempLocale;
} else {
@@ -57,7 +56,6 @@ public class LocaleConfiguration implements WebMvcConfigurer {
}
}
}
slr.setDefaultLocale(defaultLocale);
return slr;
}

View File

@@ -1,6 +1,5 @@
package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -15,15 +14,19 @@ import stirling.software.SPDF.model.ApplicationProperties;
@Configuration
public class OpenApiConfig {
@Autowired ApplicationProperties applicationProperties;
private final ApplicationProperties applicationProperties;
public OpenApiConfig(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@Bean
public OpenAPI customOpenAPI() {
String version = getClass().getPackage().getImplementationVersion();
if (version == null) {
version = "1.0.0"; // default version if all else fails
// default version if all else fails
version = "1.0.0";
}
SecurityScheme apiKeyScheme =
new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)

View File

@@ -1,6 +1,5 @@
package stirling.software.SPDF.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
@@ -9,7 +8,11 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired private EndpointInterceptor endpointInterceptor;
private final EndpointInterceptor endpointInterceptor;
public WebMvcConfig(EndpointInterceptor endpointInterceptor) {
this.endpointInterceptor = endpointInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {

View File

@@ -6,6 +6,7 @@ import java.util.List;
import stirling.software.SPDF.utils.FileInfo;
public interface DatabaseBackupInterface {
void exportDatabase() throws IOException;
boolean importDatabase();

View File

@@ -2,7 +2,6 @@ package stirling.software.SPDF.config.security;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@@ -15,8 +14,15 @@ import stirling.software.SPDF.repository.UserRepository;
@Service
class AppUpdateAuthService implements ShowAdminInterface {
@Autowired private UserRepository userRepository;
@Autowired private ApplicationProperties applicationProperties;
private final UserRepository userRepository;
private final ApplicationProperties applicationProperties;
public AppUpdateAuthService(
UserRepository userRepository, ApplicationProperties applicationProperties) {
this.userRepository = userRepository;
this.applicationProperties = applicationProperties;
}
@Override
public boolean getShowUpdateOnlyAdmins() {
@@ -24,24 +30,18 @@ class AppUpdateAuthService implements ShowAdminInterface {
if (!showUpdate) {
return showUpdate;
}
boolean showUpdateOnlyAdmin = applicationProperties.getSystem().getShowUpdateOnlyAdmin();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return !showUpdateOnlyAdmin;
}
if (authentication.getName().equalsIgnoreCase("anonymousUser")) {
return !showUpdateOnlyAdmin;
}
Optional<User> user = userRepository.findByUsername(authentication.getName());
if (user.isPresent() && showUpdateOnlyAdmin) {
return "ROLE_ADMIN".equals(user.get().getRolesAsString());
}
return showUpdate;
}
}

View File

@@ -219,9 +219,9 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
// + response.encodeRedirectURL(redirect_url);
log.info("Google does not have a specific logout URL");
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
// response.sendRedirect(googleLogoutUrl);
// break;
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
// response.sendRedirect(googleLogoutUrl);
// break;
default:
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);

View File

@@ -4,7 +4,6 @@ import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -20,9 +19,15 @@ import stirling.software.SPDF.repository.UserRepository;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired private UserRepository userRepository;
private final UserRepository userRepository;
@Autowired private LoginAttemptService loginAttemptService;
private final LoginAttemptService loginAttemptService;
public CustomUserDetailsService(
UserRepository userRepository, LoginAttemptService loginAttemptService) {
this.userRepository = userRepository;
this.loginAttemptService = loginAttemptService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
@@ -33,16 +38,13 @@ public class CustomUserDetailsService implements UserDetailsService {
() ->
new UsernameNotFoundException(
"No user found with username: " + username));
if (loginAttemptService.isBlocked(username)) {
throw new LockedException(
"Your account has been locked due to too many failed login attempts.");
}
if (!user.hasPassword()) {
throw new IllegalArgumentException("Password must not be null");
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),

View File

@@ -5,7 +5,6 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -25,7 +24,11 @@ import stirling.software.SPDF.utils.RequestUriUtils;
@Component
public class FirstLoginFilter extends OncePerRequestFilter {
@Autowired @Lazy private UserService userService;
@Lazy private final UserService userService;
public FirstLoginFilter(@Lazy UserService userService) {
this.userService = userService;
}
@Override
protected void doFilterInternal(
@@ -34,16 +37,13 @@ public class FirstLoginFilter extends OncePerRequestFilter {
String method = request.getMethod();
String requestURI = request.getRequestURI();
String contextPath = request.getContextPath();
// Check if the request is for static resources
boolean isStaticResource = RequestUriUtils.isStaticResource(contextPath, requestURI);
// If it's a static resource, just continue the filter chain and skip the logic below
if (isStaticResource) {
filterChain.doFilter(request, response);
return;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
Optional<User> user = userService.findByUsernameIgnoreCase(authentication.getName());
@@ -55,12 +55,10 @@ public class FirstLoginFilter extends OncePerRequestFilter {
return;
}
}
if (log.isDebugEnabled()) {
HttpSession session = request.getSession(true);
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
String creationTime = timeFormat.format(new Date(session.getCreationTime()));
log.debug(
"Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}",
session.isNew(),

View File

@@ -4,11 +4,7 @@ import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import stirling.software.SPDF.utils.RequestUriUtils;

View File

@@ -3,7 +3,6 @@ package stirling.software.SPDF.config.security;
import java.io.IOException;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
@@ -16,11 +15,20 @@ import stirling.software.SPDF.model.Role;
@Slf4j
public class InitialSecuritySetup {
@Autowired private UserService userService;
private final UserService userService;
@Autowired private ApplicationProperties applicationProperties;
private final ApplicationProperties applicationProperties;
@Autowired private DatabaseBackupInterface databaseBackupHelper;
private final DatabaseBackupInterface databaseBackupHelper;
public InitialSecuritySetup(
UserService userService,
ApplicationProperties applicationProperties,
DatabaseBackupInterface databaseBackupHelper) {
this.userService = userService;
this.applicationProperties = applicationProperties;
this.databaseBackupHelper = databaseBackupHelper;
}
@PostConstruct
public void init() throws IllegalArgumentException, IOException {
@@ -30,6 +38,7 @@ public class InitialSecuritySetup {
initializeAdminUser();
} else {
databaseBackupHelper.exportDatabase();
userService.migrateOauth2ToSSO();
}
initializeInternalApiUser();
}
@@ -74,5 +83,6 @@ public class InitialSecuritySetup {
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
}
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
}
}

View File

@@ -3,7 +3,6 @@ package stirling.software.SPDF.config.security;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
@@ -15,13 +14,20 @@ import stirling.software.SPDF.model.AttemptCounter;
@Slf4j
public class LoginAttemptService {
@Autowired private ApplicationProperties applicationProperties;
private final ApplicationProperties applicationProperties;
private int MAX_ATTEMPT;
private long ATTEMPT_INCREMENT_TIME;
private ConcurrentHashMap<String, AttemptCounter> attemptsCache;
private boolean isBlockedEnabled = true;
public LoginAttemptService(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@PostConstruct
public void init() {
MAX_ATTEMPT = applicationProperties.getSecurity().getLoginAttemptCount();
@@ -46,7 +52,6 @@ public class LoginAttemptService {
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
return;
}
AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
if (attemptCounter == null) {
attemptCounter = new AttemptCounter();
@@ -67,20 +72,18 @@ public class LoginAttemptService {
if (attemptCounter == null) {
return false;
}
return attemptCounter.getAttemptCount() >= MAX_ATTEMPT;
}
public int getRemainingAttempts(String key) {
if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {
return Integer.MAX_VALUE; // Arbitrarily high number if tracking is disabled
// Arbitrarily high number if tracking is disabled
return Integer.MAX_VALUE;
}
AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());
if (attemptCounter == null) {
return MAX_ATTEMPT;
}
return MAX_ATTEMPT - attemptCounter.getAttemptCount();
}
}

View File

@@ -3,14 +3,15 @@ package stirling.software.SPDF.config.security;
import java.security.cert.X509Certificate;
import java.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -32,7 +33,8 @@ import org.springframework.security.saml2.provider.service.authentication.OpenSa
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
@@ -41,6 +43,7 @@ import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
@@ -59,44 +62,77 @@ import stirling.software.SPDF.model.provider.GithubProvider;
import stirling.software.SPDF.model.provider.GoogleProvider;
import stirling.software.SPDF.model.provider.KeycloakProvider;
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
import stirling.software.SPDF.repository.PersistentLoginRepository;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@Slf4j
@DependsOn("runningEE")
public class SecurityConfiguration {
@Autowired private CustomUserDetailsService userDetailsService;
private final CustomUserDetailsService userDetailsService;
@Lazy private final UserService userService;
@Qualifier("loginEnabled")
private final boolean loginEnabledValue;
@Qualifier("runningEE")
private final boolean runningEE;
private final ApplicationProperties applicationProperties;
private final UserAuthenticationFilter userAuthenticationFilter;
private final LoginAttemptService loginAttemptService;
private final FirstLoginFilter firstLoginFilter;
private final SessionPersistentRegistry sessionRegistry;
private final PersistentLoginRepository persistentLoginRepository;
// // Only Dev test
// @Bean
// public WebSecurityCustomizer webSecurityCustomizer() {
// return (web) ->
// web.ignoring()
// .requestMatchers(
// "/css/**", "/images/**", "/js/**", "/**.svg",
// "/pdfjs-legacy/**");
// }
public SecurityConfiguration(
PersistentLoginRepository persistentLoginRepository,
CustomUserDetailsService userDetailsService,
@Lazy UserService userService,
@Qualifier("loginEnabled") boolean loginEnabledValue,
@Qualifier("runningEE") boolean runningEE,
ApplicationProperties applicationProperties,
UserAuthenticationFilter userAuthenticationFilter,
LoginAttemptService loginAttemptService,
FirstLoginFilter firstLoginFilter,
SessionPersistentRegistry sessionRegistry) {
this.userDetailsService = userDetailsService;
this.userService = userService;
this.loginEnabledValue = loginEnabledValue;
this.runningEE = runningEE;
this.applicationProperties = applicationProperties;
this.userAuthenticationFilter = userAuthenticationFilter;
this.loginAttemptService = loginAttemptService;
this.firstLoginFilter = firstLoginFilter;
this.sessionRegistry = sessionRegistry;
this.persistentLoginRepository = persistentLoginRepository;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired @Lazy private UserService userService;
@Autowired
@Qualifier("loginEnabled")
public boolean loginEnabledValue;
@Autowired ApplicationProperties applicationProperties;
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
@Autowired private LoginAttemptService loginAttemptService;
@Autowired private FirstLoginFilter firstLoginFilter;
@Autowired private SessionPersistentRegistry sessionRegistry;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
http.csrf(csrf -> csrf.disable());
}
if (loginEnabledValue) {
http.addFilterBefore(
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
if (applicationProperties.getSecurity().getCsrfDisabled()) {
http.csrf(csrf -> csrf.disable());
} else {
if (!applicationProperties.getSecurity().getCsrfDisabled()) {
CookieCsrfTokenRepository cookieRepo =
CookieCsrfTokenRepository.withHttpOnlyFalse();
CsrfTokenRequestAttributeHandler requestHandler =
@@ -106,14 +142,12 @@ public class SecurityConfiguration {
csrf ->
csrf.ignoringRequestMatchers(
request -> {
String apiKey = request.getHeader("X-API-Key");
String apiKey = request.getHeader("X-API-KEY");
// If there's no API key, don't ignore CSRF
// (return false)
if (apiKey == null || apiKey.trim().isEmpty()) {
return false;
}
// Validate API key using existing UserService
try {
Optional<User> user =
@@ -142,7 +176,6 @@ public class SecurityConfiguration {
.maxSessionsPreventsLogin(false)
.sessionRegistry(sessionRegistry)
.expiredUrl("/login?logout=true"));
http.authenticationProvider(daoAuthenticationProvider());
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
http.logout(
@@ -151,18 +184,23 @@ public class SecurityConfiguration {
.logoutSuccessHandler(
new CustomLogoutSuccessHandler(applicationProperties))
.clearAuthentication(true)
.invalidateHttpSession(true) // Invalidate session
.invalidateHttpSession( // Invalidate session
true)
.deleteCookies("JSESSIONID", "remember-me"));
http.rememberMe(
rememberMeConfigurer ->
rememberMeConfigurer // Use the configurator directly
rememberMeConfigurer -> // Use the configurator directly
rememberMeConfigurer
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(14 * 24 * 60 * 60) // 14 days
.userDetailsService(
userDetailsService) // Your existing UserDetailsService
.useSecureCookie(true) // Enable secure cookie
.rememberMeParameter("remember-me") // Form parameter name
.rememberMeCookieName("remember-me") // Cookie name
.tokenValiditySeconds( // 14 days
14 * 24 * 60 * 60)
.userDetailsService( // Your existing UserDetailsService
userDetailsService)
.useSecureCookie( // Enable secure cookie
true)
.rememberMeParameter( // Form parameter name
"remember-me")
.rememberMeCookieName( // Cookie name
"remember-me")
.alwaysRemember(false));
http.authorizeHttpRequests(
authz ->
@@ -170,14 +208,12 @@ public class SecurityConfiguration {
req -> {
String uri = req.getRequestURI();
String contextPath = req.getContextPath();
// Remove the context path from the URI
String trimmedUri =
uri.startsWith(contextPath)
? uri.substring(
contextPath.length())
: uri;
return trimmedUri.startsWith("/login")
|| trimmedUri.startsWith("/oauth")
|| trimmedUri.startsWith("/saml2")
@@ -195,7 +231,6 @@ public class SecurityConfiguration {
.permitAll()
.anyRequest()
.authenticated());
// Handle User/Password Logins
if (applicationProperties.getSecurity().isUserPass()) {
http.formLogin(
@@ -211,27 +246,26 @@ public class SecurityConfiguration {
.defaultSuccessUrl("/")
.permitAll());
}
// Handle OAUTH2 Logins
if (applicationProperties.getSecurity().isOauth2Activ()) {
http.oauth2Login(
oauth2 ->
oauth2.loginPage("/oauth2")
.
/*
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
is set as true, else login fails with an error message advising the same.
*/
.successHandler(
successHandler(
new CustomOAuth2AuthenticationSuccessHandler(
loginAttemptService,
applicationProperties,
userService))
.failureHandler(
new CustomOAuth2AuthenticationFailureHandler())
// Add existing Authorities from the database
.userInfoEndpoint(
. // Add existing Authorities from the database
userInfoEndpoint(
userInfoEndpoint ->
userInfoEndpoint
.oidcUserService(
@@ -243,14 +277,23 @@ public class SecurityConfiguration {
userAuthoritiesMapper()))
.permitAll());
}
// Handle SAML
if (applicationProperties.getSecurity().isSaml2Activ()
&& applicationProperties.getSystem().getEnableAlphaFunctionality()) {
http.authenticationProvider(samlAuthenticationProvider());
http.saml2Login(
saml2 ->
if (applicationProperties.getSecurity().isSaml2Activ()) {
// && runningEE
// Configure the authentication provider
OpenSaml4AuthenticationProvider authenticationProvider =
new OpenSaml4AuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter(
new CustomSaml2ResponseAuthenticationConverter(userService));
http.authenticationProvider(authenticationProvider)
.saml2Login(
saml2 -> {
try {
saml2.loginPage("/saml2")
.relyingPartyRegistrationRepository(
relyingPartyRegistrations())
.authenticationManager(
new ProviderManager(authenticationProvider))
.successHandler(
new CustomSaml2AuthenticationSuccessHandler(
loginAttemptService,
@@ -258,44 +301,32 @@ public class SecurityConfiguration {
userService))
.failureHandler(
new CustomSaml2AuthenticationFailureHandler())
.permitAll())
.addFilterBefore(
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
.authenticationRequestResolver(
authenticationRequestResolver(
relyingPartyRegistrations()));
} catch (Exception e) {
log.error("Error configuring SAML2 login", e);
throw new RuntimeException(e);
}
});
}
} else {
if (applicationProperties.getSecurity().getCsrfDisabled()) {
http.csrf(csrf -> csrf.disable());
} else {
CookieCsrfTokenRepository cookieRepo =
CookieCsrfTokenRepository.withHttpOnlyFalse();
CsrfTokenRequestAttributeHandler requestHandler =
new CsrfTokenRequestAttributeHandler();
requestHandler.setCsrfRequestAttributeName(null);
http.csrf(
csrf ->
csrf.csrfTokenRepository(cookieRepo)
.csrfTokenRequestHandler(requestHandler));
}
// if (!applicationProperties.getSecurity().getCsrfDisabled()) {
// CookieCsrfTokenRepository cookieRepo =
// CookieCsrfTokenRepository.withHttpOnlyFalse();
// CsrfTokenRequestAttributeHandler requestHandler =
// new CsrfTokenRequestAttributeHandler();
// requestHandler.setCsrfRequestAttributeName(null);
// http.csrf(
// csrf ->
// csrf.csrfTokenRepository(cookieRepo)
// .csrfTokenRequestHandler(requestHandler));
// }
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
}
return http.build();
}
@Bean
@ConditionalOnProperty(
name = "security.saml2.enabled",
havingValue = "true",
matchIfMissing = false)
public AuthenticationProvider samlAuthenticationProvider() {
OpenSaml4AuthenticationProvider authenticationProvider =
new OpenSaml4AuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter(
new CustomSaml2ResponseAuthenticationConverter(userService));
return authenticationProvider;
}
// Client Registration Repository for OAUTH2 OIDC Login
@Bean
@ConditionalOnProperty(
value = "security.oauth2.enabled",
@@ -303,17 +334,14 @@ public class SecurityConfiguration {
matchIfMissing = false)
public ClientRegistrationRepository clientRegistrationRepository() {
List<ClientRegistration> registrations = new ArrayList<>();
githubClientRegistration().ifPresent(registrations::add);
oidcClientRegistration().ifPresent(registrations::add);
googleClientRegistration().ifPresent(registrations::add);
keycloakClientRegistration().ifPresent(registrations::add);
if (registrations.isEmpty()) {
log.error("At least one OAuth2 provider must be configured");
System.exit(1);
}
return new InMemoryClientRegistrationRepository(registrations);
}
@@ -356,7 +384,6 @@ public class SecurityConfiguration {
return Optional.empty();
}
KeycloakProvider keycloak = client.getKeycloak();
return keycloak != null && keycloak.isSettingsValid()
? Optional.of(
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
@@ -371,7 +398,6 @@ public class SecurityConfiguration {
}
private Optional<ClientRegistration> githubClientRegistration() {
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
if (oauth == null || !oauth.getEnabled()) {
return Optional.empty();
@@ -432,39 +458,97 @@ public class SecurityConfiguration {
havingValue = "true",
matchIfMissing = false)
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
Resource privateKeyResource = samlConf.getPrivateKey();
Resource certificateResource = samlConf.getSpCert();
Saml2X509Credential signingCredential =
new Saml2X509Credential(
CertificateUtils.readPrivateKey(privateKeyResource),
CertificateUtils.readCertificate(certificateResource),
Saml2X509CredentialType.SIGNING);
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
RelyingPartyRegistration rp =
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
.signingX509Credentials((c) -> c.add(signingCredential))
.assertingPartyDetails(
(details) ->
details.entityId(samlConf.getIdpIssuer())
.signingX509Credentials(c -> c.add(signingCredential))
.assertingPartyMetadata(
metadata ->
metadata.entityId(samlConf.getIdpIssuer())
.singleSignOnServiceLocation(
samlConf.getIdpSingleLoginUrl())
.verificationX509Credentials(
(c) -> c.add(verificationCredential))
c -> c.add(verificationCredential))
.singleSignOnServiceBinding(
Saml2MessageBinding.POST)
.wantAuthnRequestsSigned(true))
.build();
return new InMemoryRelyingPartyRegistrationRepository(rp);
}
@Bean
@ConditionalOnProperty(
name = "security.saml2.enabled",
havingValue = "true",
matchIfMissing = false)
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
OpenSaml4AuthenticationRequestResolver resolver =
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
resolver.setAuthnRequestCustomizer(
customizer -> {
log.debug("Customizing SAML Authentication request");
AuthnRequest authnRequest = customizer.getAuthnRequest();
log.debug("AuthnRequest ID: {}", authnRequest.getID());
if (authnRequest.getID() == null) {
authnRequest.setID("ARQ" + UUID.randomUUID().toString());
}
log.debug("AuthnRequest new ID after set: {}", authnRequest.getID());
log.debug("AuthnRequest IssueInstant: {}", authnRequest.getIssueInstant());
log.debug(
"AuthnRequest Issuer: {}",
authnRequest.getIssuer() != null
? authnRequest.getIssuer().getValue()
: "null");
HttpServletRequest request = customizer.getRequest();
// Log HTTP request details
log.debug("HTTP Request Method: {}", request.getMethod());
log.debug("Request URI: {}", request.getRequestURI());
log.debug("Request URL: {}", request.getRequestURL().toString());
log.debug("Query String: {}", request.getQueryString());
log.debug("Remote Address: {}", request.getRemoteAddr());
// Log headers
Collections.list(request.getHeaderNames())
.forEach(
headerName -> {
log.debug(
"Header - {}: {}",
headerName,
request.getHeader(headerName));
});
// Log SAML specific parameters
log.debug("SAML Request Parameters:");
log.debug("SAMLRequest: {}", request.getParameter("SAMLRequest"));
log.debug("RelayState: {}", request.getParameter("RelayState"));
// Log session debugrmation if exists
if (request.getSession(false) != null) {
log.debug("Session ID: {}", request.getSession().getId());
}
// Log any assertions consumer service details if present
if (authnRequest.getAssertionConsumerServiceURL() != null) {
log.debug(
"AssertionConsumerServiceURL: {}",
authnRequest.getAssertionConsumerServiceURL());
}
// Log NameID policy if present
if (authnRequest.getNameIDPolicy() != null) {
log.debug(
"NameIDPolicy Format: {}",
authnRequest.getNameIDPolicy().getFormat());
}
});
return resolver;
}
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
@@ -484,12 +568,10 @@ public class SecurityConfiguration {
GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(
authority -> {
// Add existing OAUTH2 Authorities
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
// Add Authorities from database for existing user, if user is present.
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
String useAsUsername =
@@ -516,27 +598,18 @@ public class SecurityConfiguration {
@Bean
public IPRateLimitingFilter rateLimitingFilter() {
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
// Example limit TODO add config level
int maxRequestsPerIp = 1000000;
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
return new JPATokenRepositoryImpl();
return new JPATokenRepositoryImpl(persistentLoginRepository);
}
@Bean
public boolean activSecurity() {
return true;
}
// // Only Dev test
// @Bean
// public WebSecurityCustomizer webSecurityCustomizer() {
// return (web) ->
// web.ignoring()
// .requestMatchers(
// "/css/**", "/images/**", "/js/**", "/**.svg",
// "/pdfjs-legacy/**");
// }
}

View File

@@ -71,7 +71,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
// Check for API key in the request headers if no authentication exists
if (authentication == null || !authentication.isAuthenticated()) {
String apiKey = request.getHeader("X-API-Key");
String apiKey = request.getHeader("X-API-KEY");
if (apiKey != null && !apiKey.trim().isEmpty()) {
try {
// Use API key to authenticate. This requires you to have an authentication

View File

@@ -5,14 +5,12 @@ import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -31,13 +29,15 @@ import stirling.software.SPDF.model.Role;
public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
private final Map<String, Bucket> apiBuckets = new ConcurrentHashMap<>();
private final Map<String, Bucket> webBuckets = new ConcurrentHashMap<>();
@Autowired private UserDetailsService userDetailsService;
@Autowired
@Qualifier("rateLimit")
public boolean rateLimit;
private final boolean rateLimit;
public UserBasedRateLimitingFilter(@Qualifier("rateLimit") boolean rateLimit) {
this.rateLimit = rateLimit;
}
@Override
protected void doFilterInternal(
@@ -48,21 +48,18 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
return;
}
String method = request.getMethod();
if (!"POST".equalsIgnoreCase(method)) {
// If the request is not a POST, just pass it through without rate limiting
filterChain.doFilter(request, response);
return;
}
String identifier = null;
// Check for API key in the request headers
String apiKey = request.getHeader("X-API-Key");
String apiKey = request.getHeader("X-API-KEY");
if (apiKey != null && !apiKey.trim().isEmpty()) {
identifier =
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
identifier = // Prefix to distinguish between API keys and usernames
"API_KEY_" + apiKey;
} else {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
@@ -70,16 +67,13 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
identifier = userDetails.getUsername();
}
}
// If neither API key nor an authenticated user is present, use IP address
if (identifier == null) {
identifier = request.getRemoteAddr();
}
Role userRole =
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
if (request.getHeader("X-API-Key") != null) {
if (request.getHeader("X-API-KEY") != null) {
// It's an API call
processRequest(
userRole.getApiCallsPerDay(),
@@ -123,7 +117,6 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
throws IOException, ServletException {
Bucket userBucket = buckets.computeIfAbsent(identifier, k -> createUserBucket(limitPerDay));
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
if (probe.isConsumed()) {
response.setHeader(
"X-Rate-Limit-Remaining",

View File

@@ -4,7 +4,6 @@ import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -18,17 +17,14 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.Authority;
import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User;
import stirling.software.SPDF.model.*;
import stirling.software.SPDF.repository.AuthorityRepository;
import stirling.software.SPDF.repository.UserRepository;
@@ -36,22 +32,50 @@ import stirling.software.SPDF.repository.UserRepository;
@Slf4j
public class UserService implements UserServiceInterface {
@Autowired private UserRepository userRepository;
private final UserRepository userRepository;
@Autowired private AuthorityRepository authorityRepository;
private final AuthorityRepository authorityRepository;
@Autowired private PasswordEncoder passwordEncoder;
private final PasswordEncoder passwordEncoder;
@Autowired private MessageSource messageSource;
private final MessageSource messageSource;
@Autowired private SessionPersistentRegistry sessionRegistry;
private final SessionPersistentRegistry sessionRegistry;
@Autowired DatabaseBackupInterface databaseBackupHelper;
private final DatabaseBackupInterface databaseBackupHelper;
@Autowired ApplicationProperties applicationProperties;
private final ApplicationProperties applicationProperties;
public UserService(
UserRepository userRepository,
AuthorityRepository authorityRepository,
PasswordEncoder passwordEncoder,
MessageSource messageSource,
SessionPersistentRegistry sessionRegistry,
DatabaseBackupInterface databaseBackupHelper,
ApplicationProperties applicationProperties) {
this.userRepository = userRepository;
this.authorityRepository = authorityRepository;
this.passwordEncoder = passwordEncoder;
this.messageSource = messageSource;
this.sessionRegistry = sessionRegistry;
this.databaseBackupHelper = databaseBackupHelper;
this.applicationProperties = applicationProperties;
}
@Transactional
public void migrateOauth2ToSSO() {
userRepository
.findByAuthenticationTypeIgnoreCase("OAUTH2")
.forEach(
user -> {
user.setAuthenticationType(AuthenticationType.SSO);
userRepository.save(user);
});
}
// Handle OAUTH2 login and user auto creation.
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
public boolean processSSOPostLogin(String username, boolean autoCreateUser)
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
return false;
@@ -61,7 +85,7 @@ public class UserService implements UserServiceInterface {
return true;
}
if (autoCreateUser) {
saveUser(username, AuthenticationType.OAUTH2);
saveUser(username, AuthenticationType.SSO);
return true;
}
return false;
@@ -72,13 +96,11 @@ public class UserService implements UserServiceInterface {
if (!user.isPresent()) {
throw new UsernameNotFoundException("API key is not valid");
}
// Convert the user into an Authentication object
return new UsernamePasswordAuthenticationToken(
user, // principal (typically the user)
null, // credentials (we don't expose the password or API key here)
getAuthorities(user.get()) // user's authorities (roles/permissions)
);
return new UsernamePasswordAuthenticationToken( // principal (typically the user)
user, // credentials (we don't expose the password or API key here)
null, // user's authorities (roles/permissions)
getAuthorities(user.get()));
}
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
@@ -92,7 +114,8 @@ public class UserService implements UserServiceInterface {
String apiKey;
do {
apiKey = UUID.randomUUID().toString();
} while (userRepository.findByApiKey(apiKey).isPresent()); // Ensure uniqueness
} while ( // Ensure uniqueness
userRepository.findByApiKey(apiKey).isPresent());
return apiKey;
}
@@ -106,7 +129,8 @@ public class UserService implements UserServiceInterface {
}
public User refreshApiKeyForUser(String username) {
return addApiKeyToUser(username); // reuse the add API key method for refreshing
// reuse the add API key method for refreshing
return addApiKeyToUser(username);
}
public String getApiKeyForUser(String username) {
@@ -126,11 +150,11 @@ public class UserService implements UserServiceInterface {
public Optional<User> loadUserByApiKey(String apiKey) {
Optional<User> user = userRepository.findByApiKey(apiKey);
if (user.isPresent()) {
return user;
}
return null; // or throw an exception
// or throw an exception
return null;
}
public boolean validateApiKeyForUser(String username, String apiKey) {
@@ -228,14 +252,12 @@ public class UserService implements UserServiceInterface {
if (userOpt.isPresent()) {
User user = userOpt.get();
Map<String, String> settingsMap = user.getSettings();
if (settingsMap == null) {
settingsMap = new HashMap<>();
}
settingsMap.clear();
settingsMap.putAll(updates);
user.setSettings(settingsMap);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
}
@@ -304,12 +326,9 @@ public class UserService implements UserServiceInterface {
boolean isValidEmail =
username.matches(
"^(?=.{1,64}@)[A-Za-z0-9]+(\\.[A-Za-z0-9_+.-]+)*@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$");
List<String> notAllowedUserList = new ArrayList<>();
notAllowedUserList.add("ALL_USERS".toLowerCase());
boolean notAllowedUser = notAllowedUserList.contains(username.toLowerCase());
return (isValidSimpleUsername || isValidEmail) && !notAllowedUser;
}
@@ -362,7 +381,6 @@ public class UserService implements UserServiceInterface {
public String getCurrentUsername() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getUsername();
} else if (principal instanceof OAuth2User) {
@@ -378,6 +396,36 @@ public class UserService implements UserServiceInterface {
}
}
@Transactional
public void syncCustomApiUser(String customApiKey) throws IOException {
if (customApiKey == null || customApiKey.trim().length() == 0) {
return;
}
String username = "CUSTOM_API_USER";
Optional<User> existingUser = findByUsernameIgnoreCase(username);
if (!existingUser.isPresent()) {
// Create new user with API role
User user = new User();
user.setUsername(username);
user.setPassword(UUID.randomUUID().toString());
user.setEnabled(true);
user.setFirstLogin(false);
user.setAuthenticationType(AuthenticationType.WEB);
user.setApiKey(customApiKey);
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
userRepository.save(user);
databaseBackupHelper.exportDatabase();
} else {
// Update API key if it has changed
User user = existingUser.get();
if (!customApiKey.equals(user.getApiKey())) {
user.setApiKey(customApiKey);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
}
}
}
@Override
public long getTotalUsersCount() {
return userRepository.count();

View File

@@ -6,12 +6,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.*;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@@ -34,6 +29,12 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String databaseUsername;
@Value("${spring.datasource.password}")
private String databasePassword;
private Path backupPath = Paths.get("configs/db/backup/");
@Override
@@ -134,7 +135,8 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
this.getBackupFilePath("backup_" + dateNow.format(myFormatObj) + ".sql");
String query = "SCRIPT SIMPLE COLUMNS DROP to ?;";
try (Connection conn = DriverManager.getConnection(url, "sa", "");
try (Connection conn =
DriverManager.getConnection(url, databaseUsername, databasePassword);
PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, insertOutputFilePath.toString());
stmt.execute();
@@ -147,7 +149,8 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
// Retrieves the H2 database version.
public String getH2Version() {
String version = "Unknown";
try (Connection conn = DriverManager.getConnection(url, "sa", "")) {
try (Connection conn =
DriverManager.getConnection(url, databaseUsername, databasePassword)) {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT H2VERSION() AS version")) {
if (rs.next()) {
@@ -189,7 +192,8 @@ public class DatabaseBackupHelper implements DatabaseBackupInterface {
private boolean executeDatabaseScript(Path scriptPath) {
String query = "RUNSCRIPT from ?;";
try (Connection conn = DriverManager.getConnection(url, "sa", "");
try (Connection conn =
DriverManager.getConnection(url, databaseUsername, databasePassword);
PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setString(1, scriptPath.toString());
stmt.execute();

View File

@@ -2,14 +2,17 @@ package stirling.software.SPDF.config.security.database;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTasks {
@Autowired private DatabaseBackupHelper databaseBackupService;
private final DatabaseBackupHelper databaseBackupService;
public ScheduledTasks(DatabaseBackupHelper databaseBackupService) {
this.databaseBackupService = databaseBackupService;
}
@Scheduled(cron = "0 0 0 * * ?")
public void performBackup() throws IOException {

View File

@@ -82,8 +82,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
}
if (userService.usernameExistsIgnoreCase(username)
&& userService.hasPassword(username)
&& !userService.isAuthenticationTypeByUsername(
username, AuthenticationType.OAUTH2)
&& !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO)
&& oAuth.getAutoCreateUser()) {
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
return;
@@ -95,7 +94,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
return;
}
if (principal instanceof OAuth2User) {
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
userService.processSSOPostLogin(username, oAuth.getAutoCreateUser());
}
response.sendRedirect(contextPath + "/");
return;

View File

@@ -2,8 +2,6 @@ package stirling.software.SPDF.config.security.oauth2;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
@@ -13,6 +11,7 @@ import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.LoginAttemptService;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties;
@@ -20,6 +19,7 @@ import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
import stirling.software.SPDF.model.User;
@Slf4j
public class CustomOAuth2UserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
private final OidcUserService delegate = new OidcUserService();
@@ -30,8 +30,6 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
private ApplicationProperties applicationProperties;
private static final Logger logger = LoggerFactory.getLogger(CustomOAuth2UserService.class);
public CustomOAuth2UserService(
ApplicationProperties applicationProperties,
UserService userService,
@@ -82,10 +80,10 @@ public class CustomOAuth2UserService implements OAuth2UserService<OidcUserReques
user.getUserInfo(),
usernameAttribute);
} catch (IllegalArgumentException e) {
logger.error("Error loading OIDC user: {}", e.getMessage());
log.error("Error loading OIDC user: {}", e.getMessage());
throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);
} catch (Exception e) {
logger.error("Unexpected error loading OIDC user", e);
log.error("Unexpected error loading OIDC user", e);
throw new OAuth2AuthenticationException("Unexpected error during authentication");
}
}

View File

@@ -3,12 +3,14 @@ package stirling.software.SPDF.config.security.saml2;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.springframework.core.io.Resource;
@@ -28,15 +30,26 @@ public class CertificateUtils {
}
public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception {
try (PemReader pemReader =
new PemReader(
try (PEMParser pemParser =
new PEMParser(
new InputStreamReader(
privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) {
PemObject pemObject = pemReader.readPemObject();
byte[] decodedKey = pemObject.getContent();
return (RSAPrivateKey)
KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
if (object instanceof PEMKeyPair) {
// Handle traditional RSA private key format
PEMKeyPair keypair = (PEMKeyPair) object;
return (RSAPrivateKey) converter.getPrivateKey(keypair.getPrivateKeyInfo());
} else if (object instanceof PrivateKeyInfo) {
// Handle PKCS#8 format
return (RSAPrivateKey) converter.getPrivateKey((PrivateKeyInfo) object);
} else {
throw new IllegalArgumentException(
"Unsupported key format: "
+ (object != null ? object.getClass().getName() : "null"));
}
}
}
}

View File

@@ -12,6 +12,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.LoginAttemptService;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.model.ApplicationProperties;
@@ -20,11 +21,11 @@ import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.utils.RequestUriUtils;
@AllArgsConstructor
@Slf4j
public class CustomSaml2AuthenticationSuccessHandler
extends SavedRequestAwareAuthenticationSuccessHandler {
private LoginAttemptService loginAttemptService;
private ApplicationProperties applicationProperties;
private UserService userService;
@@ -34,10 +35,12 @@ public class CustomSaml2AuthenticationSuccessHandler
throws ServletException, IOException {
Object principal = authentication.getPrincipal();
log.debug("Starting SAML2 authentication success handling");
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
// Get the saved request
log.debug("Authenticated principal found for user: {}", username);
HttpSession session = request.getSession(false);
String contextPath = request.getContextPath();
SavedRequest savedRequest =
@@ -45,46 +48,77 @@ public class CustomSaml2AuthenticationSuccessHandler
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
: null;
log.debug(
"Session exists: {}, Saved request exists: {}",
session != null,
savedRequest != null);
if (savedRequest != null
&& !RequestUriUtils.isStaticResource(
contextPath, savedRequest.getRedirectUrl())) {
// Redirect to the original destination
log.debug(
"Valid saved request found, redirecting to original destination: {}",
savedRequest.getRedirectUrl());
super.onAuthenticationSuccess(request, response, authentication);
} else {
SAML2 saml2 = applicationProperties.getSecurity().getSaml2();
log.debug(
"Processing SAML2 authentication with autoCreateUser: {}",
saml2.getAutoCreateUser());
if (loginAttemptService.isBlocked(username)) {
log.debug("User {} is blocked due to too many login attempts", username);
if (session != null) {
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
}
throw new LockedException(
"Your account has been locked due to too many failed login attempts.");
}
if (userService.usernameExistsIgnoreCase(username)
&& userService.hasPassword(username)
&& !userService.isAuthenticationTypeByUsername(
username, AuthenticationType.OAUTH2)
&& saml2.getAutoCreateUser()) {
boolean userExists = userService.usernameExistsIgnoreCase(username);
boolean hasPassword = userExists && userService.hasPassword(username);
boolean isSSOUser =
userExists
&& userService.isAuthenticationTypeByUsername(
username, AuthenticationType.SSO);
log.debug(
"User status - Exists: {}, Has password: {}, Is SSO user: {}",
userExists,
hasPassword,
isSSOUser);
if (userExists && hasPassword && !isSSOUser && saml2.getAutoCreateUser()) {
log.debug(
"User {} exists with password but is not SSO user, redirecting to logout",
username);
response.sendRedirect(
contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
return;
}
try {
if (saml2.getBlockRegistration()
&& !userService.usernameExistsIgnoreCase(username)) {
if (saml2.getBlockRegistration() && !userExists) {
log.debug("Registration blocked for new user: {}", username);
response.sendRedirect(
contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
return;
}
userService.processOAuth2PostLogin(username, saml2.getAutoCreateUser());
log.debug("Processing SSO post-login for user: {}", username);
userService.processSSOPostLogin(username, saml2.getAutoCreateUser());
log.debug("Successfully processed authentication for user: {}", username);
response.sendRedirect(contextPath + "/");
return;
} catch (IllegalArgumentException e) {
log.debug(
"Invalid username detected for user: {}, redirecting to logout",
username);
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
return;
}
}
} else {
log.debug("Non-SAML2 principal detected, delegating to parent handler");
super.onAuthenticationSuccess(request, response, authentication);
}
}

View File

@@ -3,8 +3,6 @@ package stirling.software.SPDF.config.security.saml2;
import java.util.*;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.schema.XSBoolean;
import org.opensaml.core.xml.schema.XSString;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.core.AttributeStatement;
@@ -30,15 +28,60 @@ public class CustomSaml2ResponseAuthenticationConverter
this.userService = userService;
}
private Map<String, List<Object>> extractAttributes(Assertion assertion) {
Map<String, List<Object>> attributes = new HashMap<>();
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
for (Attribute attribute : attributeStatement.getAttributes()) {
String attributeName = attribute.getName();
List<Object> values = new ArrayList<>();
for (XMLObject xmlObject : attribute.getAttributeValues()) {
// Get the text content directly
String value = xmlObject.getDOM().getTextContent();
if (value != null && !value.trim().isEmpty()) {
values.add(value);
}
}
if (!values.isEmpty()) {
// Store with both full URI and last part of the URI
attributes.put(attributeName, values);
String shortName = attributeName.substring(attributeName.lastIndexOf('/') + 1);
attributes.put(shortName, values);
}
}
}
return attributes;
}
@Override
public Saml2Authentication convert(ResponseToken responseToken) {
// Extract the assertion from the response
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
Map<String, List<Object>> attributes = extractAttributes(assertion);
// Extract the NameID
String nameId = assertion.getSubject().getNameID().getValue();
// Debug log with actual values
log.debug("Extracted SAML Attributes: " + attributes);
Optional<User> userOpt = userService.findByUsernameIgnoreCase(nameId);
// Try to get username/identifier in order of preference
String userIdentifier = null;
if (hasAttribute(attributes, "username")) {
userIdentifier = getFirstAttributeValue(attributes, "username");
} else if (hasAttribute(attributes, "emailaddress")) {
userIdentifier = getFirstAttributeValue(attributes, "emailaddress");
} else if (hasAttribute(attributes, "name")) {
userIdentifier = getFirstAttributeValue(attributes, "name");
} else if (hasAttribute(attributes, "upn")) {
userIdentifier = getFirstAttributeValue(attributes, "upn");
} else if (hasAttribute(attributes, "uid")) {
userIdentifier = getFirstAttributeValue(attributes, "uid");
} else {
userIdentifier = assertion.getSubject().getNameID().getValue();
}
// Rest of your existing code...
Optional<User> userOpt = userService.findByUsernameIgnoreCase(userIdentifier);
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
if (userOpt.isPresent()) {
User user = userOpt.get();
@@ -48,39 +91,27 @@ public class CustomSaml2ResponseAuthenticationConverter
}
}
// Extract the SessionIndexes
List<String> sessionIndexes = new ArrayList<>();
for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {
sessionIndexes.add(authnStatement.getSessionIndex());
}
// Extract the Attributes
Map<String, List<Object>> attributes = extractAttributes(assertion);
// Create the custom principal
CustomSaml2AuthenticatedPrincipal principal =
new CustomSaml2AuthenticatedPrincipal(nameId, attributes, nameId, sessionIndexes);
new CustomSaml2AuthenticatedPrincipal(
userIdentifier, attributes, userIdentifier, sessionIndexes);
// Create the Saml2Authentication
return new Saml2Authentication(
principal,
responseToken.getToken().getSaml2Response(),
Collections.singletonList(simpleGrantedAuthority));
}
private Map<String, List<Object>> extractAttributes(Assertion assertion) {
Map<String, List<Object>> attributes = new HashMap<>();
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
for (Attribute attribute : attributeStatement.getAttributes()) {
String attributeName = attribute.getName();
List<Object> values = new ArrayList<>();
for (XMLObject xmlObject : attribute.getAttributeValues()) {
log.info("BOOL: " + ((XSBoolean) xmlObject).getValue());
values.add(((XSString) xmlObject).getValue());
}
attributes.put(attributeName, values);
}
}
return attributes;
private boolean hasAttribute(Map<String, List<Object>> attributes, String name) {
return attributes.containsKey(name) && !attributes.get(name).isEmpty();
}
private String getFirstAttributeValue(Map<String, List<Object>> attributes, String name) {
List<Object> values = attributes.get(name);
return values != null && !values.isEmpty() ? values.get(0).toString() : null;
}
}

View File

@@ -1,12 +1,7 @@
package stirling.software.SPDF.config.security.session;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.session.SessionInformation;

View File

@@ -5,19 +5,22 @@ import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.stereotype.Component;
@Component
public class SessionScheduled {
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
private final SessionPersistentRegistry sessionPersistentRegistry;
public SessionScheduled(SessionPersistentRegistry sessionPersistentRegistry) {
this.sessionPersistentRegistry = sessionPersistentRegistry;
}
@Scheduled(cron = "0 0/5 * * * ?")
public void expireSessions() {
Instant now = Instant.now();
for (Object principal : sessionPersistentRegistry.getAllPrincipals()) {
List<SessionInformation> sessionInformations =
sessionPersistentRegistry.getAllSessions(principal, false);

View File

@@ -4,7 +4,6 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -18,35 +17,35 @@ import stirling.software.SPDF.service.LanguageService;
@RequestMapping("/js")
public class AdditionalLanguageJsController {
@Autowired private LanguageService languageService;
private final LanguageService languageService;
public AdditionalLanguageJsController(LanguageService languageService) {
this.languageService = languageService;
}
@Hidden
@GetMapping(value = "/additionalLanguageCode.js", produces = "application/javascript")
public void generateAdditionalLanguageJs(HttpServletResponse response) throws IOException {
List<String> supportedLanguages = languageService.getSupportedLanguages();
response.setContentType("application/javascript");
PrintWriter writer = response.getWriter();
// Erstelle das JavaScript dynamisch
writer.println("const supportedLanguages = " + toJsonArray(supportedLanguages) + ";");
// Generiere die `getDetailedLanguageCode`-Funktion
writer.println(
"""
function getDetailedLanguageCode() {
const userLanguages = navigator.languages ? navigator.languages : [navigator.language];
for (let lang of userLanguages) {
let matchedLang = supportedLanguages.find(supportedLang => supportedLang.startsWith(lang.replace('-', '_')));
if (matchedLang) {
return matchedLang;
function getDetailedLanguageCode() {
const userLanguages = navigator.languages ? navigator.languages : [navigator.language];
for (let lang of userLanguages) {
let matchedLang = supportedLanguages.find(supportedLang => supportedLang.startsWith(lang.replace('-', '_')));
if (matchedLang) {
return matchedLang;
}
}
// Fallback
return "en_GB";
}
}
// Fallback
return "en_GB";
}
""");
""");
writer.flush();
}

View File

@@ -11,8 +11,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -33,8 +31,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class CropController {
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
private final PostHogService postHogService;

View File

@@ -8,23 +8,19 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.eclipse.jetty.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
@@ -34,19 +30,25 @@ import stirling.software.SPDF.config.security.database.DatabaseBackupHelper;
@Controller
@RequestMapping("/api/v1/database")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Tag(name = "Database", description = "Database APIs")
@Tag(name = "Database", description = "Database APIs for backup, import, and management")
public class DatabaseController {
@Autowired DatabaseBackupHelper databaseBackupHelper;
private final DatabaseBackupHelper databaseBackupHelper;
public DatabaseController(DatabaseBackupHelper databaseBackupHelper) {
this.databaseBackupHelper = databaseBackupHelper;
}
@Hidden
@PostMapping(consumes = "multipart/form-data", value = "import-database")
@Operation(
summary = "Import database backup",
description = "This endpoint imports a database backup from a SQL file.")
summary = "Import a database backup file",
description = "Uploads and imports a database backup SQL file.")
@PostMapping(consumes = "multipart/form-data", value = "import-database")
public String importDatabase(
@RequestParam("fileInput") MultipartFile file, RedirectAttributes redirectAttributes)
throws IllegalArgumentException, IOException {
@Parameter(description = "SQL file to import", required = true)
@RequestParam("fileInput")
MultipartFile file,
RedirectAttributes redirectAttributes)
throws IOException {
if (file == null || file.isEmpty()) {
redirectAttributes.addAttribute("error", "fileNullOrEmpty");
return "redirect:/database";
@@ -69,13 +71,17 @@ public class DatabaseController {
}
@Hidden
@Operation(
summary = "Import database backup by filename",
description = "Imports a database backup file from the server using its file name.")
@GetMapping("/import-database-file/{fileName}")
public String importDatabaseFromBackupUI(@PathVariable String fileName)
throws IllegalArgumentException, IOException {
public String importDatabaseFromBackupUI(
@Parameter(description = "Name of the file to import", required = true) @PathVariable
String fileName)
throws IOException {
if (fileName == null || fileName.isEmpty()) {
return "redirect:/database?error=fileNullOrEmpty";
}
// Check if the file exists in the backup list
boolean fileExists =
databaseBackupHelper.getBackupList().stream()
@@ -93,12 +99,13 @@ public class DatabaseController {
}
@Hidden
@GetMapping("/delete/{fileName}")
@Operation(
summary = "Delete a database backup file",
description =
"This endpoint deletes a database backup file with the specified file name.")
public String deleteFile(@PathVariable String fileName) {
description = "Deletes a specified database backup file from the server.")
@GetMapping("/delete/{fileName}")
public String deleteFile(
@Parameter(description = "Name of the file to delete", required = true) @PathVariable
String fileName) {
if (fileName == null || fileName.isEmpty()) {
throw new IllegalArgumentException("File must not be null or empty");
}
@@ -117,12 +124,13 @@ public class DatabaseController {
}
@Hidden
@GetMapping("/download/{fileName}")
@Operation(
summary = "Download a database backup file",
description =
"This endpoint downloads a database backup file with the specified file name.")
public ResponseEntity<?> downloadFile(@PathVariable String fileName) {
description = "Downloads the specified database backup file from the server.")
@GetMapping("/download/{fileName}")
public ResponseEntity<?> downloadFile(
@Parameter(description = "Name of the file to download", required = true) @PathVariable
String fileName) {
if (fileName == null || fileName.isEmpty()) {
throw new IllegalArgumentException("File must not be null or empty");
}
@@ -141,4 +149,22 @@ public class DatabaseController {
.build();
}
}
@Operation(
summary = "Create a database backup",
description =
"This endpoint triggers the creation of a database backup and redirects to the"
+ " database management page.")
@GetMapping("/createDatabaseBackup")
public String createDatabaseBackup() {
try {
log.info("Starting database backup creation...");
databaseBackupHelper.exportDatabase();
log.info("Database backup successfully created.");
} catch (IOException e) {
log.error("Error creating database backup: {}", e.getMessage(), e);
return "redirect:/database?error=" + e.getMessage();
}
return "redirect:/database?infoMessage=backupCreated";
}
}

View File

@@ -20,8 +20,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -33,18 +31,18 @@ import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.general.MergePdfsRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@Slf4j
@RequestMapping("/api/v1/general")
@Tag(name = "General", description = "General APIs")
public class MergeController {
private static final Logger logger = LoggerFactory.getLogger(MergeController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -184,7 +182,7 @@ public class MergeController {
baos.toByteArray(), mergedFileName); // Return the modified PDF
} catch (Exception ex) {
logger.error("Error in merge pdf process", ex);
log.error("Error in merge pdf process", ex);
throw ex;
} finally {
for (File file : filesToDelete) {

View File

@@ -1,6 +1,6 @@
package stirling.software.SPDF.controller.api;
import java.awt.Color;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -12,8 +12,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.util.Matrix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -35,8 +33,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class MultiPageLayoutController {
private static final Logger logger = LoggerFactory.getLogger(MultiPageLayoutController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired

View File

@@ -6,7 +6,10 @@ import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

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