Compare commits

...

232 Commits
mac ... cosign

Author SHA1 Message Date
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
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
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
196 changed files with 5668 additions and 3304 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

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

@@ -8,14 +8,14 @@ jobs:
check-comment:
runs-on: ubuntu-latest
if: |
github.event.issue.pull_request &&
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 == 'frooodle' ||
github.event.comment.user.login == 'sf298' ||
github.event.comment.user.login == 'Ludy87' ||
github.event.comment.user.login == 'LaserKaspar' ||
@@ -28,9 +28,14 @@ jobs:
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@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const prNumber = context.payload.issue.number;
@@ -39,41 +44,46 @@ jobs:
- name: Get PR repository and ref
id: get-pr-info
uses: actions/github-script@v7
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@v4
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@v4
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: '17'
distribution: 'temurin'
@@ -84,20 +94,20 @@ jobs:
DOCKER_ENABLE_SECURITY: false
- name: Set up Docker 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: Build and push PR-specific image
uses: docker/build-push-action@v6
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
context: .
file: ./Dockerfile
@@ -146,10 +156,10 @@ jobs:
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
@@ -158,7 +168,7 @@ jobs:
- name: Post deployment URL to PR
if: success()
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const { GITHUB_REPOSITORY } = process.env;

View File

@@ -4,9 +4,7 @@ on:
pull_request:
types: [opened, synchronize, reopened, closed]
permissions:
contents: write
pull-requests: write
permissions: read-all
env:
SERVER_IP: ${{ secrets.VPS_IP }} # Add this to your GitHub secrets
@@ -15,9 +13,17 @@ env:
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/
@@ -30,20 +36,20 @@ jobs:
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"
@@ -51,7 +57,7 @@ jobs:
fi
ENDSSH
)
if [[ $CLEANUP_STATUS == *"PERFORMED_CLEANUP"* ]]; then
echo "cleanup_performed=true" >> $GITHUB_OUTPUT
else
@@ -60,7 +66,7 @@ jobs:
- name: Post cleanup notice to PR
if: steps.cleanup.outputs.cleanup_performed == 'true'
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const { GITHUB_REPOSITORY } = process.env;

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,17 +21,22 @@ 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
uses: gradle/actions/setup-gradle@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
with:
gradle-version: 8.7
@@ -56,17 +61,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 +84,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,37 +1,42 @@
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:
branches: ["main"]
paths:
- "src/main/resources/messages_en_GB.properties"
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@v4
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
env:
PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }}
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
ref: $PULL_REQUEST_REF
path: pr-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"
@@ -109,7 +114,7 @@ jobs:
- name: Post comment on PR
if: env.SCRIPT_OUTPUT != ''
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
@@ -155,59 +160,3 @@ jobs:
run: |
echo "Failing the job because errors were detected."
exit 1
update-translations-main:
if: github.event_name == 'push'
permissions:
contents: write
pull-requests: write
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
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@v7
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
labels: Translation
draft: false
delete-branch: true
sign-commits: true

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@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update 3rd Party Licenses"
@@ -72,7 +77,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@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.0
with:
files: ./Stirling-PDF-${{ matrix.platform }}-installer.${{ matrix.ext }}

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,7 +75,7 @@ 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
@@ -75,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: .
@@ -87,10 +101,32 @@ 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 }}
run: |
# Always sign images regardless of branch
echo "$TAGS" | tr ',' '\n' | while read -r tag; do
cosign sign --yes "${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 --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: |
@@ -103,7 +139,8 @@ jobs:
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: .
@@ -115,10 +152,22 @@ 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 }}
run: |
echo "$TAGS" | tr ',' '\n' | while read -r tag; do
cosign sign --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: |
@@ -131,7 +180,8 @@ jobs:
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 }}
@@ -144,3 +194,15 @@ 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 }}
run: |
echo "$TAGS" | tr ',' '\n' | while read -r tag; do
cosign sign --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@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.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@7b4da11513bf3f43f9999e90eabced41ab8bb048 # v2.2.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@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update files

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
labels: Translation
draft: false
delete-branch: true
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

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

@@ -1,5 +1,5 @@
# Main stage
FROM alpine:3.20.3
FROM alpine:3.20.3@sha256:1e42bbe2508154c9126d48c2b8a75420c3544343bf86fd041fb7527e017a4b4a
# Copy necessary files
COPY scripts /scripts

View File

@@ -1,5 +1,5 @@
# Build the application
FROM gradle:8.11-jdk17 AS build
FROM gradle:8.12-jdk17 AS build
# Set the working directory
WORKDIR /app

View File

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

View File

@@ -2,7 +2,7 @@
<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)
@@ -11,8 +11,6 @@
[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)
@@ -191,44 +189,44 @@ Stirling-PDF currently supports 38 languages!
| Language | Progress |
| -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![95%](https://geps.dev/progress/95) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![93%](https://geps.dev/progress/93) |
| Basque (Euskara) (eu_ES) | ![54%](https://geps.dev/progress/54) |
| Bulgarian (Български) (bg_BG) | ![90%](https://geps.dev/progress/90) |
| Catalan (Català) (ca_CA) | ![85%](https://geps.dev/progress/85) |
| Croatian (Hrvatski) (hr_HR) | ![92%](https://geps.dev/progress/92) |
| Czech (Česky) (cs_CZ) | ![91%](https://geps.dev/progress/91) |
| Danish (Dansk) (da_DK) | ![90%](https://geps.dev/progress/90) |
| Dutch (Nederlands) (nl_NL) | ![90%](https://geps.dev/progress/90) |
| 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) | ![93%](https://geps.dev/progress/93) |
| French (Français) (fr_FR) | ![92%](https://geps.dev/progress/92) |
| German (Deutsch) (de_DE) | ![100%](https://geps.dev/progress/100) |
| Greek (Ελληνικά) (el_GR) | ![91%](https://geps.dev/progress/91) |
| Hindi (हिंदी) (hi_IN) | ![89%](https://geps.dev/progress/89) |
| Hungarian (Magyar) (hu_HU) | ![92%](https://geps.dev/progress/92) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![91%](https://geps.dev/progress/91) |
| Irish (Gaeilge) (ga_IE) | ![83%](https://geps.dev/progress/83) |
| 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) | ![81%](https://geps.dev/progress/81) |
| Korean (한국어) (ko_KR) | ![90%](https://geps.dev/progress/90) |
| Norwegian (Norsk) (no_NB) | ![83%](https://geps.dev/progress/83) |
| Persian (فارسی) (fa_IR) | ![100%](https://geps.dev/progress/100) |
| Polish (Polski) (pl_PL) | ![91%](https://geps.dev/progress/91) |
| Portuguese (Português) (pt_PT) | ![91%](https://geps.dev/progress/91) |
| Portuguese Brazilian (Português) (pt_BR) | ![92%](https://geps.dev/progress/92) |
| Romanian (Română) (ro_RO) | ![85%](https://geps.dev/progress/85) |
| Russian (Русский) (ru_RU) | ![91%](https://geps.dev/progress/91) |
| 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) | ![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) | ![86%](https://geps.dev/progress/86) |
| 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) | ![92%](https://geps.dev/progress/92) |
| Swedish (Svenska) (sv_SE) | ![91%](https://geps.dev/progress/91) |
| Thai (ไทย) (th_TH) | ![91%](https://geps.dev/progress/91) |
| Traditional Chinese (繁體中文) (zh_TW) | ![92%](https://geps.dev/progress/92) |
| Turkish (Türkçe) (tr_TR) | ![87%](https://geps.dev/progress/87) |
| 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) | ![84%](https://geps.dev/progress/84) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![83%](https://geps.dev/progress/83) |
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
@@ -405,7 +403,7 @@ To access your account settings, go to Account Settings in the settings cog menu
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

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,13 +1,14 @@
plugins {
id "java"
id "org.springframework.boot" version "3.4.0"
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"
}
@@ -15,18 +16,18 @@ plugins {
import com.github.jk1.license.render.*
ext {
springBootVersion = "3.4.0"
springBootVersion = "3.4.1"
pdfboxVersion = "3.0.3"
logbackVersion = "1.5.7"
imageioVersion = "3.12.0"
lombokVersion = "1.18.36"
bouncycastleVersion = "1.79"
springSecuritySamlVersion = "6.4.1"
openSamlVersion = "4.3.2"
springSecuritySamlVersion = "6.4.2"
openSamlVersion = "4.3.2"
}
group = "stirling.software"
version = "0.36.0"
version = "0.36.5"
java {
@@ -37,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 {
@@ -64,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/**"
}
}
}
}
@@ -74,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"
@@ -100,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()
@@ -123,10 +266,17 @@ configurations.all {
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
dependencies {
//security updates
implementation "org.springframework:spring-webmvc:6.2.0"
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'
@@ -142,12 +292,12 @@ 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.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"
@@ -218,7 +368,7 @@ dependencies {
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.14.1"
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"
@@ -271,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

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

@@ -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.11-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

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

@@ -28,7 +28,7 @@ public class LicenseKeyChecker {
this.checkLicense();
}
@Scheduled(initialDelay = 604800000,fixedRate = 604800000) // 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,7 +13,6 @@ 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();
@@ -87,7 +83,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

@@ -1,5 +1,6 @@
package stirling.software.SPDF;
import java.awt.*;
import java.io.IOException;
import java.net.ServerSocket;
import java.nio.file.Files;
@@ -8,9 +9,10 @@ import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.swing.*;
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,15 +23,17 @@ 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;
@@ -67,36 +71,19 @@ 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<>();
@@ -105,7 +92,7 @@ public class SPdfApplication {
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"))) {
@@ -118,16 +105,22 @@ 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
@@ -135,16 +128,56 @@ public class SPdfApplication {
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);
}
@Autowired(required = false)
private WebBrowser webBrowser;
@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 static String getStaticBaseUrl() {

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,8 +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;
@@ -21,14 +19,14 @@ 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);
@Autowired ApplicationProperties applicationProperties;
@Bean
@@ -61,7 +59,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 +99,27 @@ 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

@@ -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,19 +7,19 @@ 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 Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
@@ -43,7 +43,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 +87,7 @@ public class EndpointConfiguration {
.collect(Collectors.toList());
if (!disabledList.isEmpty()) {
logger.info(
log.info(
"Total disabled endpoints: {}. Disabled endpoints: {}",
disabledList.size(),
String.join(", ", disabledList));
@@ -260,6 +260,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() {

View File

@@ -1,11 +1,14 @@
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;
@@ -23,6 +26,18 @@ public class InitialSetup {
@Autowired private 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)) {
@@ -32,7 +47,6 @@ public class InitialSetup {
}
}
@PostConstruct
public void initSecretKey() throws IOException {
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
if (!GeneralUtils.isValidUUID(secretKey)) {
@@ -42,13 +56,24 @@ public class InitialSetup {
}
}
@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);
}
@@ -56,8 +81,23 @@ public class InitialSetup {
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

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

@@ -75,5 +75,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

@@ -99,7 +99,7 @@ public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
if (applicationProperties.getSecurity().getCsrfDisabled()) {
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
http.csrf(csrf -> csrf.disable());
}
@@ -116,7 +116,7 @@ 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)
@@ -289,17 +289,17 @@ public class SecurityConfiguration {
}
} else {
if (!applicationProperties.getSecurity().getCsrfDisabled()) {
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());
}

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

@@ -59,7 +59,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
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
@@ -79,7 +79,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
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(),

View File

@@ -390,6 +390,37 @@ 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

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

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

@@ -25,6 +25,7 @@ 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,24 +35,28 @@ 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;
@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";
}
log.info("Received file: {}", file.getOriginalFilename());
Path tempTemplatePath = Files.createTempFile("backup_", ".sql");
try (InputStream in = file.getInputStream()) {
Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);
@@ -69,9 +74,15 @@ 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";
}
@@ -85,6 +96,7 @@ public class DatabaseController {
return "redirect:/database?error=fileNotFound";
}
log.info("Received file: {}", fileName);
if (databaseBackupHelper.importDatabaseFromUI(fileName)) {
log.info("File {} imported to database", fileName);
return "redirect:/database?infoMessage=importIntoDatabaseSuccessed";
@@ -93,12 +105,14 @@ 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 +131,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 +156,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

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

@@ -8,8 +8,6 @@ import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
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;
@@ -22,6 +20,7 @@ import io.github.pixee.security.Filenames;
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.SortTypes;
import stirling.software.SPDF.model.api.PDFWithPageNums;
import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
@@ -31,11 +30,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Slf4j
@Tag(name = "General", description = "General APIs")
public class RearrangePagesPDFController {
private static final Logger logger = LoggerFactory.getLogger(RearrangePagesPDFController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -202,7 +200,7 @@ public class RearrangePagesPDFController {
throw new IllegalArgumentException("Unsupported custom mode");
}
} catch (IllegalArgumentException e) {
logger.error("Unsupported custom mode", e);
log.error("Unsupported custom mode", e);
return null;
}
}
@@ -230,8 +228,8 @@ public class RearrangePagesPDFController {
} else {
newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);
}
logger.info("newPageOrder = " + newPageOrder);
logger.info("totalPages = " + totalPages);
log.info("newPageOrder = " + newPageOrder);
log.info("totalPages = " + totalPages);
// Create a new list to hold the pages in the new order
List<PDPage> newPages = new ArrayList<>();
for (int i = 0; i < newPageOrder.size(); i++) {
@@ -254,7 +252,7 @@ public class RearrangePagesPDFController {
.replaceFirst("[.][^.]+$", "")
+ "_rearranged.pdf");
} catch (IOException e) {
logger.error("Failed rearranging documents", e);
log.error("Failed rearranging documents", e);
return null;
}
}

View File

@@ -5,8 +5,6 @@ import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
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;
@@ -28,8 +26,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class RotationController {
private static final Logger logger = LoggerFactory.getLogger(RotationController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired

View File

@@ -13,8 +13,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;
@@ -36,8 +34,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class ScalePagesController {
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired

View File

@@ -13,8 +13,6 @@ import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -28,16 +26,17 @@ import io.github.pixee.security.Filenames;
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.PDFWithPageNums;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Slf4j
@Tag(name = "General", description = "General APIs")
public class SplitPDFController {
private static final Logger logger = LoggerFactory.getLogger(SplitPDFController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -52,84 +51,114 @@ public class SplitPDFController {
"This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
throws IOException {
MultipartFile file = request.getFileInput();
String pages = request.getPageNumbers();
// open the pdf document
PDDocument document = Loader.loadPDF(file.getBytes());
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
if (!pageNumbers.contains(totalPages - 1)) {
// Create a mutable ArrayList so we can add to it
pageNumbers = new ArrayList<>(pageNumbers);
pageNumbers.add(totalPages - 1);
}
logger.info(
"Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
PDDocument document = null;
Path zipFile = null;
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int previousPageNumber = 0;
for (int splitPoint : pageNumbers) {
try (PDDocument splitDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
for (int i = previousPageNumber; i <= splitPoint; i++) {
PDPage page = document.getPage(i);
splitDocument.addPage(page);
logger.info("Adding page {} to split document", i);
try {
MultipartFile file = request.getFileInput();
String pages = request.getPageNumbers();
// open the pdf document
document = Loader.loadPDF(file.getBytes());
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
int totalPages = document.getNumberOfPages();
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
if (!pageNumbers.contains(totalPages - 1)) {
// Create a mutable ArrayList so we can add to it
pageNumbers = new ArrayList<>(pageNumbers);
pageNumbers.add(totalPages - 1);
}
log.info(
"Splitting PDF into pages: {}",
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
// split the document
splitDocumentsBoas = new ArrayList<>();
int previousPageNumber = 0;
for (int splitPoint : pageNumbers) {
try (PDDocument splitDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
for (int i = previousPageNumber; i <= splitPoint; i++) {
PDPage page = document.getPage(i);
splitDocument.addPage(page);
log.info("Adding page {} to split document", i);
}
previousPageNumber = splitPoint + 1;
// Transfer metadata to split pdf
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
splitDocumentsBoas.add(baos);
} catch (Exception e) {
log.error("Failed splitting documents and saving them", e);
throw e;
}
previousPageNumber = splitPoint + 1;
}
// Transfer metadata to split pdf
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
// closing the original document
document.close();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
splitDocument.save(baos);
zipFile = Files.createTempFile("split_documents", ".zip");
splitDocumentsBoas.add(baos);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = filename + "_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
log.info("Wrote split document {} to zip file", fileName);
}
} catch (Exception e) {
logger.error("Failed splitting documents and saving them", e);
log.error("Failed writing to zip", e);
throw e;
}
}
// closing the original document
document.close();
log.info("Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
Path zipFile = Files.createTempFile("split_documents", ".zip");
// return the Resource in the response
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
// loop through the split documents and write them to the zip file
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
String fileName = filename + "_" + (i + 1) + ".pdf";
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
byte[] pdf = baos.toByteArray();
} finally {
try {
// Close the main document
if (document != null) {
document.close();
}
// Add PDF file to the zip
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry();
// Close all ByteArrayOutputStreams
for (ByteArrayOutputStream baos : splitDocumentsBoas) {
if (baos != null) {
baos.close();
}
}
logger.info("Wrote split document {} to zip file", fileName);
// Delete temporary zip file
if (zipFile != null) {
Files.deleteIfExists(zipFile);
}
} catch (Exception e) {
log.error("Error while cleaning up resources", e);
}
} catch (Exception e) {
logger.error("Failed writing to zip", e);
throw e;
}
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
// return the Resource in the response
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
}
}

View File

@@ -13,8 +13,6 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -32,6 +30,7 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
import stirling.software.SPDF.service.PdfMetadataService;
@@ -39,12 +38,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Slf4j
@Tag(name = "General", description = "General APIs")
public class SplitPdfByChaptersController {
private static final Logger logger =
LoggerFactory.getLogger(SplitPdfByChaptersController.class);
private final PdfMetadataService pdfMetadataService;
@Autowired
@@ -59,70 +56,86 @@ public class SplitPdfByChaptersController {
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
throws Exception {
MultipartFile file = request.getFileInput();
boolean includeMetadata = request.getIncludeMetadata();
Integer bookmarkLevel =
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
if (bookmarkLevel < 0) {
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
}
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
PDDocument sourceDocument = null;
Path zipFile = null;
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
if (outline == null) {
logger.warn("No outline found for {}", file.getOriginalFilename());
return ResponseEntity.badRequest().body("No outline found".getBytes());
}
List<Bookmark> bookmarks = new ArrayList<>();
try {
bookmarks =
extractOutlineItems(
sourceDocument,
outline.getFirstChild(),
bookmarks,
outline.getFirstChild().getNextSibling(),
0,
bookmarkLevel);
// to handle last page edge case
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
boolean includeMetadata = request.getIncludeMetadata();
Integer bookmarkLevel =
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
if (bookmarkLevel < 0) {
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
}
sourceDocument = Loader.loadPDF(file.getBytes());
} catch (Exception e) {
logger.error("Unable to extract outline items", e);
return ResponseEntity.internalServerError()
.body("Unable to extract outline items".getBytes());
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
if (outline == null) {
log.warn("No outline found for {}", file.getOriginalFilename());
return ResponseEntity.badRequest().body("No outline found".getBytes());
}
List<Bookmark> bookmarks = new ArrayList<>();
try {
bookmarks =
extractOutlineItems(
sourceDocument,
outline.getFirstChild(),
bookmarks,
outline.getFirstChild().getNextSibling(),
0,
bookmarkLevel);
// to handle last page edge case
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
} catch (Exception e) {
log.error("Unable to extract outline items", e);
return ResponseEntity.internalServerError()
.body("Unable to extract outline items".getBytes());
}
boolean allowDuplicates = request.getAllowDuplicates();
if (!allowDuplicates) {
/*
duplicates are generated when multiple bookmarks correspond to the same page,
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
the bookmarks that correspond to the same page, and treat them as a single bookmark
*/
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
}
for (Bookmark bookmark : bookmarks) {
log.info(
"{}::::{} to {}",
bookmark.getTitle(),
bookmark.getStartPage(),
bookmark.getEndPage());
}
List<ByteArrayOutputStream> splitDocumentsBoas =
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
zipFile = createZipFile(bookmarks, splitDocumentsBoas);
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
sourceDocument.close();
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
} finally {
try {
if (sourceDocument != null) {
sourceDocument.close();
}
if (zipFile != null) {
Files.deleteIfExists(zipFile);
}
} catch (Exception e) {
log.error("Error while cleaning up resources", e);
}
}
boolean allowDuplicates = request.getAllowDuplicates();
if (!allowDuplicates) {
/*
duplicates are generated when multiple bookmarks correspond to the same page,
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
the bookmarks that correspond to the same page, and treat them as a single bookmark
*/
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
}
for (Bookmark bookmark : bookmarks) {
logger.info(
"{}::::{} to {}",
bookmark.getTitle(),
bookmark.getStartPage(),
bookmark.getEndPage());
}
List<ByteArrayOutputStream> splitDocumentsBoas =
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
Path zipFile = createZipFile(bookmarks, splitDocumentsBoas);
byte[] data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
sourceDocument.close();
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
}
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {
@@ -240,14 +253,14 @@ public class SplitPdfByChaptersController {
zipOut.write(pdf);
zipOut.closeEntry();
logger.info("Wrote split document {} to zip file", fileName);
log.info("Wrote split document {} to zip file", fileName);
}
} catch (Exception e) {
logger.error("Failed writing to zip", e);
log.error("Failed writing to zip", e);
throw e;
}
logger.info("Successfully created zip file with split documents: {}", zipFile);
log.info("Successfully created zip file with split documents: {}", zipFile);
return zipFile;
}
@@ -268,7 +281,7 @@ public class SplitPdfByChaptersController {
i++) {
PDPage page = sourceDocument.getPage(i);
splitDocument.addPage(page);
logger.info("Adding page {} to split document", i);
log.info("Adding page {} to split document", i);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (includeMetadata) {
@@ -279,7 +292,7 @@ public class SplitPdfByChaptersController {
splitDocumentsBoas.add(baos);
} catch (Exception e) {
logger.error("Failed splitting documents and saving them", e);
log.error("Failed splitting documents and saving them", e);
throw e;
}
}

View File

@@ -18,8 +18,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
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.MediaType;
import org.springframework.http.ResponseEntity;
@@ -42,9 +40,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "General", description = "General APIs")
public class SplitPdfBySectionsController {
private static final Logger logger =
LoggerFactory.getLogger(SplitPdfBySectionsController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -105,15 +100,13 @@ public class SplitPdfBySectionsController {
if (sectionNum == horiz * verti) pageNum++;
}
} catch (Exception e) {
logger.error("exception", e);
} finally {
data = Files.readAllBytes(zipFile);
return WebResponseUtils.bytesToWebResponse(
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
} finally {
Files.deleteIfExists(zipFile);
}
return WebResponseUtils.bytesToWebResponse(
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
}
public List<PDDocument> splitPdfPages(

View File

@@ -10,8 +10,6 @@ import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -25,6 +23,7 @@ import io.github.pixee.security.Filenames;
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.SplitPdfBySizeOrCountRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
@@ -32,10 +31,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/general")
@Slf4j
@Tag(name = "General", description = "General APIs")
public class SplitPdfBySizeController {
private static final Logger logger = LoggerFactory.getLogger(SplitPdfBySizeController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -78,7 +77,7 @@ public class SplitPdfBySizeController {
}
} catch (Exception e) {
logger.error("exception", e);
log.error("exception", e);
} finally {
data = Files.readAllBytes(zipFile);
Files.deleteIfExists(zipFile);

View File

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

View File

@@ -14,8 +14,6 @@ import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.rendering.ImageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -29,6 +27,7 @@ import io.github.pixee.security.Filenames;
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.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
@@ -40,11 +39,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/convert")
@Slf4j
@Tag(name = "Convert", description = "Convert APIs")
public class ConvertImgPDFController {
private static final Logger logger = LoggerFactory.getLogger(ConvertImgPDFController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -65,112 +63,137 @@ public class ConvertImgPDFController {
String colorType = request.getColorType();
String dpi = request.getDpi();
byte[] pdfBytes = file.getBytes();
ImageType colorTypeResult = ImageType.RGB;
if ("greyscale".equals(colorType)) {
colorTypeResult = ImageType.GRAY;
} else if ("blackwhite".equals(colorType)) {
colorTypeResult = ImageType.BINARY;
}
// returns bytes for image
boolean singleImage = "single".equals(singleOrMultiple);
Path tempFile = null;
Path tempOutputDir = null;
Path tempPdfPath = null;
byte[] result = null;
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
result =
PdfUtils.convertFromPdf(
pdfBytes,
"webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(),
colorTypeResult,
singleImage,
Integer.valueOf(dpi),
filename);
if (result == null || result.length == 0) {
logger.error("resultant bytes for {} is null, error converting ", filename);
}
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
throw new IOException("Python is not installed. Required for WebP conversion.");
} else if ("webp".equalsIgnoreCase(imageFormat)
&& CheckProgramInstall.isPythonAvailable()) {
// Write the output stream to a temp file
Path tempFile = Files.createTempFile("temp_png", ".png");
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
fos.write(result);
fos.flush();
try {
byte[] pdfBytes = file.getBytes();
ImageType colorTypeResult = ImageType.RGB;
if ("greyscale".equals(colorType)) {
colorTypeResult = ImageType.GRAY;
} else if ("blackwhite".equals(colorType)) {
colorTypeResult = ImageType.BINARY;
}
// returns bytes for image
boolean singleImage = "single".equals(singleOrMultiple);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
List<String> command = new ArrayList<>();
command.add(pythonVersion);
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
// Create a temporary directory for the output WebP files
Path tempOutputDir = Files.createTempDirectory("webp_output");
if (singleImage) {
// Run the Python script to convert PNG to WebP
command.add(tempFile.toString());
command.add(tempOutputDir.toString());
command.add("--single");
} else {
// Save the uploaded PDF to a temporary file
Path tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
file.transferTo(tempPdfPath.toFile());
// Run the Python script to convert PDF to WebP
command.add(tempPdfPath.toString());
command.add(tempOutputDir.toString());
result =
PdfUtils.convertFromPdf(
pdfBytes,
"webp".equalsIgnoreCase(imageFormat)
? "png"
: imageFormat.toUpperCase(),
colorTypeResult,
singleImage,
Integer.valueOf(dpi),
filename);
if (result == null || result.length == 0) {
log.error("resultant bytes for {} is null, error converting ", filename);
}
command.add("--dpi");
command.add(dpi);
ProcessExecutorResult resultProcess =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// Find all WebP files in the output directory
List<Path> webpFiles =
Files.walk(tempOutputDir)
.filter(path -> path.toString().endsWith(".webp"))
.collect(Collectors.toList());
if (webpFiles.isEmpty()) {
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
throw new IOException("No WebP files were created. " + resultProcess.getMessages());
}
byte[] bodyBytes = new byte[0];
if (webpFiles.size() == 1) {
// Return the single WebP file directly
Path webpFilePath = webpFiles.get(0);
bodyBytes = Files.readAllBytes(webpFilePath);
} else {
// Create a ZIP file containing all WebP images
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
for (Path webpFile : webpFiles) {
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
Files.copy(webpFile, zos);
zos.closeEntry();
}
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
throw new IOException("Python is not installed. Required for WebP conversion.");
} else if ("webp".equalsIgnoreCase(imageFormat)
&& CheckProgramInstall.isPythonAvailable()) {
// Write the output stream to a temp file
tempFile = Files.createTempFile("temp_png", ".png");
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
fos.write(result);
fos.flush();
}
bodyBytes = zipOutputStream.toByteArray();
}
// Clean up the temporary files
Files.deleteIfExists(tempFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
result = bodyBytes;
}
if (singleImage) {
String docName = filename + "." + imageFormat;
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
} else {
String zipFilename = filename + "_convertedToImages.zip";
return WebResponseUtils.bytesToWebResponse(
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
List<String> command = new ArrayList<>();
command.add(pythonVersion);
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
// Create a temporary directory for the output WebP files
tempOutputDir = Files.createTempDirectory("webp_output");
if (singleImage) {
// Run the Python script to convert PNG to WebP
command.add(tempFile.toString());
command.add(tempOutputDir.toString());
command.add("--single");
} else {
// Save the uploaded PDF to a temporary file
tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
file.transferTo(tempPdfPath.toFile());
// Run the Python script to convert PDF to WebP
command.add(tempPdfPath.toString());
command.add(tempOutputDir.toString());
}
command.add("--dpi");
command.add(dpi);
ProcessExecutorResult resultProcess =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// Find all WebP files in the output directory
List<Path> webpFiles =
Files.walk(tempOutputDir)
.filter(path -> path.toString().endsWith(".webp"))
.collect(Collectors.toList());
if (webpFiles.isEmpty()) {
log.error("No WebP files were created in: {}", tempOutputDir.toString());
throw new IOException(
"No WebP files were created. " + resultProcess.getMessages());
}
byte[] bodyBytes = new byte[0];
if (webpFiles.size() == 1) {
// Return the single WebP file directly
Path webpFilePath = webpFiles.get(0);
bodyBytes = Files.readAllBytes(webpFilePath);
} else {
// Create a ZIP file containing all WebP images
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
for (Path webpFile : webpFiles) {
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
Files.copy(webpFile, zos);
zos.closeEntry();
}
}
bodyBytes = zipOutputStream.toByteArray();
}
// Clean up the temporary files
Files.deleteIfExists(tempFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
result = bodyBytes;
}
if (singleImage) {
String docName = filename + "." + imageFormat;
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
} else {
String zipFilename = filename + "_convertedToImages.zip";
return WebResponseUtils.bytesToWebResponse(
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
}
} finally {
try {
// Clean up temporary files
if (tempFile != null) {
Files.deleteIfExists(tempFile);
}
if (tempPdfPath != null) {
Files.deleteIfExists(tempPdfPath);
}
if (tempOutputDir != null) {
FileUtils.deleteDirectory(tempOutputDir.toFile());
}
} catch (Exception e) {
log.error("Error cleaning up temporary files", e);
}
}
}
@@ -185,7 +208,13 @@ public class ConvertImgPDFController {
String fitOption = request.getFitOption();
String colorType = request.getColorType();
boolean autoRotate = request.isAutoRotate();
// Handle Null entries for formdata
if (colorType == null || colorType.isBlank()) {
colorType = "color";
}
if (fitOption == null || fitOption.isEmpty()) {
fitOption = "fillPage";
}
// Convert the file to PDF and get the resulting bytes
byte[] bytes =
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);

View File

@@ -8,8 +8,6 @@ import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -22,6 +20,7 @@ import io.github.pixee.security.Filenames;
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.converters.PdfToPdfARequest;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@@ -29,11 +28,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/convert")
@Slf4j
@Tag(name = "Convert", description = "Convert APIs")
public class ConvertPDFToPDFA {
private static final Logger logger = LoggerFactory.getLogger(ConvertPDFToPDFA.class);
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
@Operation(
summary = "Convert a PDF to a PDF/A",
@@ -46,7 +44,7 @@ public class ConvertPDFToPDFA {
// Validate input file type
if (!"application/pdf".equals(inputFile.getContentType())) {
logger.error("Invalid input file type: {}", inputFile.getContentType());
log.error("Invalid input file type: {}", inputFile.getContentType());
throw new IllegalArgumentException("Input file must be a PDF");
}
@@ -96,7 +94,7 @@ public class ConvertPDFToPDFA {
.runCommandWithOutputHandling(command);
if (returnCode.getRc() != 0) {
logger.error("PDF/A conversion failed with return code: {}", returnCode.getRc());
log.error("PDF/A conversion failed with return code: {}", returnCode.getRc());
throw new RuntimeException("PDF/A conversion failed");
}

View File

@@ -7,8 +7,6 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
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;
@@ -19,6 +17,7 @@ import org.springframework.web.bind.annotation.RestController;
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.converters.UrlToPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
@@ -28,11 +27,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@Tag(name = "Convert", description = "Convert APIs")
@Slf4j
@RequestMapping("/api/v1/convert")
public class ConvertWebsiteToPDF {
private static final Logger logger = LoggerFactory.getLogger(ConvertWebsiteToPDF.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -88,7 +86,7 @@ public class ConvertWebsiteToPDF {
try {
Files.deleteIfExists(tempOutputFile);
} catch (IOException e) {
logger.error("Error deleting temporary output file", e);
log.error("Error deleting temporary output file", e);
}
}
}

View File

@@ -7,8 +7,6 @@ import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.QuoteMode;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -34,8 +32,6 @@ import technology.tabula.writers.Writer;
@Tag(name = "Convert", description = "Convert APIs")
public class ExtractCSVController {
private static final Logger logger = LoggerFactory.getLogger(ExtractCSVController.class);
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
@Operation(
summary = "Extracts a CSV document from a PDF",

View File

@@ -9,8 +9,6 @@ import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@@ -22,16 +20,16 @@ import io.github.pixee.security.Filenames;
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.misc.ExtractHeaderRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class AutoRenameController {
private static final Logger logger = LoggerFactory.getLogger(AutoRenameController.class);
private static final float TITLE_FONT_SIZE_THRESHOLD = 20.0f;
private static final int LINE_LIMIT = 200;
@@ -133,7 +131,7 @@ public class AutoRenameController {
header = header.replaceAll("[/\\\\?%*:|\"<>]", "").trim();
return WebResponseUtils.pdfDocToWebResponse(document, header + ".pdf");
} else {
logger.info("File has no good title to be found");
log.info("File has no good title to be found");
return WebResponseUtils.pdfDocToWebResponse(
document, Filenames.toSimpleFileName(file.getOriginalFilename()));
}

View File

@@ -14,8 +14,6 @@ import java.util.zip.ZipOutputStream;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -37,16 +35,17 @@ import io.github.pixee.security.Filenames;
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.misc.AutoSplitPdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class AutoSplitPdfController {
private static final Logger logger = LoggerFactory.getLogger(AutoSplitPdfController.class);
private static final String QR_CONTENT = "https://github.com/Stirling-Tools/Stirling-PDF";
private static final String QR_CONTENT_OLD = "https://github.com/Frooodle/Stirling-PDF";
@@ -134,7 +133,7 @@ public class AutoSplitPdfController {
try {
document.close();
} catch (IOException e) {
logger.error("Error closing main PDDocument", e);
log.error("Error closing main PDDocument", e);
}
}
@@ -142,7 +141,7 @@ public class AutoSplitPdfController {
try {
splitDoc.close();
} catch (IOException e) {
logger.error("Error closing split PDDocument", e);
log.error("Error closing split PDDocument", e);
}
}
@@ -150,7 +149,7 @@ public class AutoSplitPdfController {
try {
Files.deleteIfExists(zipFile);
} catch (IOException e) {
logger.error("Error deleting temporary zip file", e);
log.error("Error deleting temporary zip file", e);
}
}
}

View File

@@ -14,8 +14,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -30,6 +28,7 @@ import io.github.pixee.security.Filenames;
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.misc.RemoveBlankPagesRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;
@@ -37,11 +36,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class BlankPageController {
private static final Logger logger = LoggerFactory.getLogger(BlankPageController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -71,7 +69,7 @@ public class BlankPageController {
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
for (PDPage page : pages) {
logger.info("checking page {}", pageIndex);
log.info("checking page {}", pageIndex);
textStripper.setStartPage(pageIndex + 1);
textStripper.setEndPage(pageIndex + 1);
String pageText = textStripper.getText(document);
@@ -79,12 +77,12 @@ public class BlankPageController {
boolean blank = true;
if (hasText) {
logger.info("page {} has text, not blank", pageIndex);
log.info("page {} has text, not blank", pageIndex);
blank = false;
} else {
boolean hasImages = PdfUtils.hasImagesOnPage(page);
if (hasImages) {
logger.info("page {} has image, running blank detection", pageIndex);
log.info("page {} has image, running blank detection", pageIndex);
// Render image and save as temp file
BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 30);
blank = isBlankImage(image, threshold, whitePercent, threshold);
@@ -92,10 +90,10 @@ public class BlankPageController {
}
if (blank) {
logger.info("Skipping, Image was blank for page #{}", pageIndex);
log.info("Skipping, Image was blank for page #{}", pageIndex);
blankPages.add(page);
} else {
logger.info("page {} has image which is not blank", pageIndex);
log.info("page {} has image which is not blank", pageIndex);
nonBlankPages.add(page);
}
@@ -121,12 +119,12 @@ public class BlankPageController {
zos.close();
logger.info("Returning ZIP file: {}", filename + "_processed.zip");
log.info("Returning ZIP file: {}", filename + "_processed.zip");
return WebResponseUtils.boasToWebResponse(
baos, filename + "_processed.zip", MediaType.APPLICATION_OCTET_STREAM);
} catch (IOException e) {
logger.error("exception", e);
log.error("exception", e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@@ -149,7 +147,7 @@ public class BlankPageController {
public static boolean isBlankImage(
BufferedImage image, int threshold, double whitePercent, int blurSize) {
if (image == null) {
logger.info("Error: Image is null");
log.info("Error: Image is null");
return false;
}
@@ -167,7 +165,7 @@ public class BlankPageController {
}
double whitePixelPercentage = (whitePixels / (double) totalPixels) * 100;
logger.info(String.format("Page has white pixel percent of %.2f%%", whitePixelPercentage));
log.info(String.format("Page has white pixel percent of %.2f%%", whitePixelPercentage));
return whitePixelPercentage >= whitePercent;
}

View File

@@ -17,8 +17,6 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
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;
@@ -31,6 +29,7 @@ import io.github.pixee.security.Filenames;
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.misc.OptimizePdfRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.GeneralUtils;
@@ -40,11 +39,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class CompressController {
private static final Logger logger = LoggerFactory.getLogger(CompressController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -191,7 +189,7 @@ public class CompressController {
incrementOptimizeLevel(
optimizeLevel, outputFileSize, expectedOutputSize);
if (autoMode && optimizeLevel > 9) {
logger.info("Maximum compression level reached in auto mode");
log.info("Maximum compression level reached in auto mode");
sizeMet = true;
}
}
@@ -203,7 +201,7 @@ public class CompressController {
// Check if optimized file is larger than the original
if (pdfBytes.length > inputFileSize) {
logger.warn(
log.warn(
"Optimized file is larger than the original. Returning the original file instead.");
finalFile = tempInputFile;
}
@@ -234,7 +232,7 @@ public class CompressController {
private int incrementOptimizeLevel(int currentLevel, long currentSize, long targetSize) {
double currentRatio = currentSize / (double) targetSize;
logger.info("Current compression ratio: {}", String.format("%.2f", currentRatio));
log.info("Current compression ratio: {}", String.format("%.2f", currentRatio));
if (currentRatio > 2.0) {
return Math.min(9, currentLevel + 3);

View File

@@ -17,8 +17,6 @@ import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
@@ -31,6 +29,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
import stirling.software.SPDF.utils.CheckProgramInstall;
import stirling.software.SPDF.utils.ProcessExecutor;
@@ -39,11 +38,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class ExtractImageScansController {
private static final Logger logger = LoggerFactory.getLogger(ExtractImageScansController.class);
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
@Operation(
summary = "Extract image scans from an input file",
@@ -201,7 +199,7 @@ public class ExtractImageScansController {
try {
Files.deleteIfExists(path);
} catch (IOException e) {
logger.error("Failed to delete temporary image file: " + path, e);
log.error("Failed to delete temporary image file: " + path, e);
}
});
@@ -209,7 +207,7 @@ public class ExtractImageScansController {
try {
Files.deleteIfExists(tempZipFile);
} catch (IOException e) {
logger.error("Failed to delete temporary zip file: " + tempZipFile, e);
log.error("Failed to delete temporary zip file: " + tempZipFile, e);
}
}
@@ -218,7 +216,7 @@ public class ExtractImageScansController {
try {
FileUtils.deleteDirectory(dir.toFile());
} catch (IOException e) {
logger.error("Failed to delete temporary directory: " + dir, e);
log.error("Failed to delete temporary directory: " + dir, e);
}
});
}

View File

@@ -25,8 +25,6 @@ import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -39,17 +37,17 @@ import io.github.pixee.security.Filenames;
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.PDFExtractImagesRequest;
import stirling.software.SPDF.utils.ImageProcessingUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class ExtractImagesController {
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
@Operation(
summary = "Extract images from a PDF file",
@@ -107,7 +105,7 @@ public class ExtractImagesController {
allowDuplicates);
} catch (IOException e) {
// Log the error and continue processing other pages
logger.error(
log.error(
"Error extracting images from page {}: {}",
pageNum,
e.getMessage());
@@ -167,7 +165,7 @@ public class ExtractImagesController {
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
logger.error("MD5 algorithm not available for extractImages hash.", e);
log.error("MD5 algorithm not available for extractImages hash.", e);
return;
}
if (page.getResources() == null || page.getResources().getXObjectNames() == null) {

View File

@@ -27,8 +27,6 @@ import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@@ -50,8 +48,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class FakeScanControllerWIP {
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
// TODO finish
@PostMapping(consumes = "multipart/form-data", value = "/fake-scan")
@Hidden

View File

@@ -12,8 +12,6 @@ import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
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;
@@ -26,17 +24,17 @@ import io.github.pixee.security.Filenames;
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.misc.FlattenRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class FlattenController {
private static final Logger logger = LoggerFactory.getLogger(FlattenController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -84,7 +82,7 @@ public class FlattenController {
contentStream.drawImage(pdImage, 0, 0, pageWidth, pageHeight);
}
} catch (IOException e) {
logger.error("exception", e);
log.error("exception", e);
}
}
return WebResponseUtils.pdfDocToWebResponse(

View File

@@ -11,8 +11,6 @@ import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
@@ -26,17 +24,17 @@ import io.github.pixee.security.Filenames;
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.misc.MetadataRequest;
import stirling.software.SPDF.utils.WebResponseUtils;
import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;
@RestController
@RequestMapping("/api/v1/misc")
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class MetadataController {
private static final Logger logger = LoggerFactory.getLogger(MetadataController.class);
private String checkUndefined(String entry) {
// Check if the string is "undefined"
if ("undefined".equals(entry)) {
@@ -148,7 +146,7 @@ public class MetadataController {
creationDateCal.setTime(
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(creationDate));
} catch (ParseException e) {
logger.error("exception", e);
log.error("exception", e);
}
info.setCreationDate(creationDateCal);
} else {
@@ -160,7 +158,7 @@ public class MetadataController {
modificationDateCal.setTime(
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(modificationDate));
} catch (ParseException e) {
logger.error("exception", e);
log.error("exception", e);
}
info.setModificationDate(modificationDateCal);
} else {

View File

@@ -87,7 +87,7 @@ public class OCRController {
Files.createDirectories(tempOutputDir);
Files.createDirectories(tempImagesDir);
Process process = null;
try {
// Save input file
inputFile.transferTo(tempInputFile.toFile());
@@ -139,7 +139,7 @@ public class OCRController {
command.add("pdf"); // Always output PDF
ProcessBuilder pb = new ProcessBuilder(command);
Process process = pb.start();
process = pb.start();
// Capture any error output
try (BufferedReader reader =
@@ -188,6 +188,10 @@ public class OCRController {
.body(pdfContent);
} finally {
if (process != null) {
process.destroy();
}
// Clean up temporary files
deleteDirectory(tempDir);
}

View File

@@ -2,8 +2,6 @@ package stirling.software.SPDF.controller.api.misc;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -17,6 +15,7 @@ import io.github.pixee.security.Filenames;
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.misc.OverlayImageRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.PdfUtils;
@@ -24,11 +23,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Slf4j
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class OverlayImageController {
private static final Logger logger = LoggerFactory.getLogger(OverlayImageController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -60,7 +58,7 @@ public class OverlayImageController {
.replaceFirst("[.][^.]+$", "")
+ "_overlayed.pdf");
} catch (IOException e) {
logger.error("Failed to add image to PDF", e);
log.error("Failed to add image to PDF", e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}

View File

@@ -10,8 +10,6 @@ import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -35,8 +33,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class PageNumbersController {
private static final Logger logger = LoggerFactory.getLogger(PageNumbersController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired

View File

@@ -6,8 +6,6 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
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;
@@ -31,8 +29,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class RepairController {
private static final Logger logger = LoggerFactory.getLogger(RepairController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired

View File

@@ -7,8 +7,6 @@ import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -29,8 +27,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class ShowJavascript {
private static final Logger logger = LoggerFactory.getLogger(ShowJavascript.class);
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
@Operation(
summary = "Grabs all JS from a PDF and returns a single JS file with all code",

View File

@@ -7,8 +7,6 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@@ -21,17 +19,17 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletContext;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.SPdfApplication;
import stirling.software.SPDF.model.ApiEndpoint;
import stirling.software.SPDF.model.Role;
@Service
@Slf4j
public class ApiDocService {
private final Map<String, ApiEndpoint> apiDocumentation = new HashMap<>();
private static final Logger logger = LoggerFactory.getLogger(ApiDocService.class);
@Autowired private ServletContext servletContext;
private String getApiDocsUrl() {
@@ -135,7 +133,7 @@ public class ApiDocService {
});
} catch (Exception e) {
// Handle exceptions
logger.error("Error grabbing swagger doc, body result {}", apiDocsJson);
log.error("Error grabbing swagger doc, body result {}", apiDocsJson);
}
}

View File

@@ -8,8 +8,6 @@ import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
@@ -26,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.api.HandleDataRequest;
@@ -33,11 +32,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/pipeline")
@Slf4j
@Tag(name = "Pipeline", description = "Pipeline APIs")
public class PipelineController {
private static final Logger logger = LoggerFactory.getLogger(PipelineController.class);
final String watchedFoldersDir = "./pipeline/watchedFolders/";
final String finishedFoldersDir = "./pipeline/finishedFolders/";
@Autowired PipelineProcessor processor;
@@ -56,7 +54,7 @@ public class PipelineController {
return null;
}
PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class);
logger.info("Received POST request to /handleData with {} files", files.length);
log.info("Received POST request to /handleData with {} files", files.length);
try {
List<Resource> inputFiles = processor.generateInputFiles(files);
if (inputFiles == null || inputFiles.size() == 0) {
@@ -71,7 +69,7 @@ public class PipelineController {
is.read(bytes);
is.close();
logger.info("Returning single file response...");
log.info("Returning single file response...");
return WebResponseUtils.bytesToWebResponse(
bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM);
} else if (outputFiles == null) {
@@ -118,11 +116,11 @@ public class PipelineController {
zipOut.close();
logger.info("Returning zipped file response...");
log.info("Returning zipped file response...");
return WebResponseUtils.boasToWebResponse(
baos, "output.zip", MediaType.APPLICATION_OCTET_STREAM);
} catch (Exception e) {
logger.error("Error handling data: ", e);
log.error("Error handling data: ", e);
return null;
}
}

View File

@@ -16,8 +16,6 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
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.core.io.ByteArrayResource;
@@ -27,14 +25,15 @@ import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.utils.FileMonitor;
@Service
@Slf4j
public class PipelineDirectoryProcessor {
private static final Logger logger = LoggerFactory.getLogger(PipelineDirectoryProcessor.class);
@Autowired private ObjectMapper objectMapper;
@Autowired private ApiDocService apiDocService;
@Autowired PipelineProcessor processor;
@@ -56,9 +55,9 @@ public class PipelineDirectoryProcessor {
if (!Files.exists(watchedFolderPath)) {
try {
Files.createDirectories(watchedFolderPath);
logger.info("Created directory: {}", watchedFolderPath);
log.info("Created directory: {}", watchedFolderPath);
} catch (IOException e) {
logger.error("Error creating directory: {}", watchedFolderPath, e);
log.error("Error creating directory: {}", watchedFolderPath, e);
return;
}
}
@@ -71,21 +70,21 @@ public class PipelineDirectoryProcessor {
handleDirectory(t);
}
} catch (Exception e) {
logger.error("Error handling directory: {}", t, e);
log.error("Error handling directory: {}", t, e);
}
});
} catch (Exception e) {
logger.error("Error walking through directory: {}", watchedFolderPath, e);
log.error("Error walking through directory: {}", watchedFolderPath, e);
}
}
public void handleDirectory(Path dir) throws IOException {
logger.info("Handling directory: {}", dir);
log.info("Handling directory: {}", dir);
Path processingDir = createProcessingDirectory(dir);
Optional<Path> jsonFileOptional = findJsonFile(dir);
if (!jsonFileOptional.isPresent()) {
logger.warn("No .JSON settings file found. No processing will happen for dir {}.", dir);
log.warn("No .JSON settings file found. No processing will happen for dir {}.", dir);
return;
}
@@ -98,7 +97,7 @@ public class PipelineDirectoryProcessor {
Path processingDir = dir.resolve("processing");
if (!Files.exists(processingDir)) {
Files.createDirectory(processingDir);
logger.info("Created processing directory: {}", processingDir);
log.info("Created processing directory: {}", processingDir);
}
return processingDir;
}
@@ -111,7 +110,7 @@ public class PipelineDirectoryProcessor {
private PipelineConfig readAndParseJson(Path jsonFile) throws IOException {
String jsonString = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);
logger.debug("Reading JSON file: {}", jsonFile);
log.debug("Reading JSON file: {}", jsonFile);
return objectMapper.readValue(jsonString, PipelineConfig.class);
}
@@ -121,7 +120,7 @@ public class PipelineDirectoryProcessor {
validateOperation(operation);
File[] files = collectFilesForProcessing(dir, jsonFile, operation);
if (files == null || files.length == 0) {
logger.debug("No files detected for {} ", dir);
log.debug("No files detected for {} ", dir);
return;
}
List<File> filesToProcess = prepareFilesForProcessing(files, processingDir);
@@ -202,7 +201,7 @@ public class PipelineDirectoryProcessor {
moveAndRenameFiles(outputFiles, config, dir);
deleteOriginalFiles(filesToProcess, processingDir);
} catch (Exception e) {
logger.error("error during processing", e);
log.error("error during processing", e);
moveFilesBack(filesToProcess, processingDir);
}
}
@@ -215,7 +214,7 @@ public class PipelineDirectoryProcessor {
if (!Files.exists(outputPath)) {
Files.createDirectories(outputPath);
logger.info("Created directory: {}", outputPath);
log.info("Created directory: {}", outputPath);
}
Path outputFile = outputPath.resolve(outputFileName);
@@ -223,7 +222,7 @@ public class PipelineDirectoryProcessor {
os.write(((ByteArrayResource) resource).getByteArray());
}
logger.info("File moved and renamed to {}", outputFile);
log.info("File moved and renamed to {}", outputFile);
}
}
@@ -264,7 +263,7 @@ public class PipelineDirectoryProcessor {
throws IOException {
for (File file : filesToProcess) {
Files.deleteIfExists(processingDir.resolve(file.getName()));
logger.info("Deleted original file: {}", file.getName());
log.info("Deleted original file: {}", file.getName());
}
}
@@ -272,12 +271,12 @@ public class PipelineDirectoryProcessor {
for (File file : filesToProcess) {
try {
Files.move(processingDir.resolve(file.getName()), file.toPath());
logger.info(
log.info(
"Moved file back to original location: {} , {}",
file.toPath(),
file.getName());
} catch (IOException e) {
logger.error("Error moving file back to original location: {}", file.getName(), e);
log.error("Error moving file back to original location: {}", file.getName(), e);
}
}
}

View File

@@ -19,8 +19,6 @@ import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
@@ -40,16 +38,16 @@ import io.github.pixee.security.Filenames;
import io.github.pixee.security.ZipSecurity;
import jakarta.servlet.ServletContext;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.SPdfApplication;
import stirling.software.SPDF.model.PipelineConfig;
import stirling.software.SPDF.model.PipelineOperation;
import stirling.software.SPDF.model.Role;
@Service
@Slf4j
public class PipelineProcessor {
private static final Logger logger = LoggerFactory.getLogger(PipelineProcessor.class);
@Autowired private ApiDocService apiDocService;
@Autowired(required = false)
@@ -81,7 +79,7 @@ public class PipelineProcessor {
String operation = pipelineOperation.getOperation();
boolean isMultiInputOperation = apiDocService.isMultiInput(operation);
logger.info(
log.info(
"Running operation: {} isMultiInputOperation {}",
operation,
isMultiInputOperation);
@@ -124,7 +122,7 @@ public class PipelineProcessor {
if (operation.startsWith("filter-")
&& (response.getBody() == null
|| response.getBody().length == 0)) {
logger.info("Skipping file due to failing {}", operation);
log.info("Skipping file due to failing {}", operation);
continue;
}
@@ -208,7 +206,7 @@ public class PipelineProcessor {
outputFiles = newOutputFiles;
}
if (hasErrors) {
logger.error("Errors occurred during processing. Log: {}", logStream.toString());
log.error("Errors occurred during processing. Log: {}", logStream.toString());
}
return outputFiles;
@@ -221,7 +219,7 @@ public class PipelineProcessor {
HttpHeaders headers = new HttpHeaders();
String apiKey = getApiKeyForUser();
headers.add("X-API-Key", apiKey);
headers.add("X-API-KEY", apiKey);
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// Create HttpEntity with the body and headers
@@ -310,7 +308,7 @@ public class PipelineProcessor {
List<Resource> generateInputFiles(File[] files) throws Exception {
if (files == null || files.length == 0) {
logger.info("No files");
log.info("No files");
return null;
}
@@ -318,7 +316,7 @@ public class PipelineProcessor {
for (File file : files) {
Path path = Paths.get(file.getAbsolutePath());
logger.info("Reading file: " + path); // debug statement
log.info("Reading file: " + path); // debug statement
if (Files.exists(path)) {
Resource fileResource =
@@ -330,16 +328,16 @@ public class PipelineProcessor {
};
outputFiles.add(fileResource);
} else {
logger.info("File not found: " + path);
log.info("File not found: " + path);
}
}
logger.info("Files successfully loaded. Starting processing...");
log.info("Files successfully loaded. Starting processing...");
return outputFiles;
}
List<Resource> generateInputFiles(MultipartFile[] files) throws Exception {
if (files == null || files.length == 0) {
logger.info("No files");
log.info("No files");
return null;
}
@@ -355,7 +353,7 @@ public class PipelineProcessor {
};
outputFiles.add(fileResource);
}
logger.info("Files successfully loaded. Starting processing...");
log.info("Files successfully loaded. Starting processing...");
return outputFiles;
}
@@ -369,7 +367,7 @@ public class PipelineProcessor {
}
private List<Resource> unzip(byte[] data) throws IOException {
logger.info("Unzipping data of length: {}", data.length);
log.info("Unzipping data of length: {}", data.length);
List<Resource> unzippedFiles = new ArrayList<>();
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
@@ -396,7 +394,7 @@ public class PipelineProcessor {
// If the unzipped file is a zip file, unzip it
if (isZip(baos.toByteArray())) {
logger.info("File {} is a zip file. Unzipping...", filename);
log.info("File {} is a zip file. Unzipping...", filename);
unzippedFiles.addAll(unzip(baos.toByteArray()));
} else {
unzippedFiles.add(fileResource);
@@ -404,7 +402,7 @@ public class PipelineProcessor {
}
}
logger.info("Unzipping completed. {} files were unzipped.", unzippedFiles.size());
log.info("Unzipping completed. {} files were unzipped.", unzippedFiles.size());
return unzippedFiles;
}
}

View File

@@ -63,8 +63,6 @@ import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity;
@@ -78,17 +76,17 @@ import io.github.pixee.security.Filenames;
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.security.SignPDFWithCertRequest;
import stirling.software.SPDF.service.CustomPDDocumentFactory;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/security")
@Slf4j
@Tag(name = "Security", description = "Security APIs")
public class CertSignController {
private static final Logger logger = LoggerFactory.getLogger(CertSignController.class);
static {
Security.addProvider(new BouncyCastleProvider());
}
@@ -108,7 +106,7 @@ public class CertSignController {
logoFile = Files.createTempFile("signature", ".png").toFile();
FileUtils.copyInputStreamToFile(is, logoFile);
} catch (IOException e) {
logger.error("Failed to load image signature file");
log.error("Failed to load image signature file");
throw e;
}
}
@@ -212,7 +210,9 @@ public class CertSignController {
@Operation(
summary = "Sign PDF with a Digital Certificate",
description =
"This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
"This endpoint accepts a PDF file, a digital certificate and related information to sign"
+ " the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF"
+ " Type:SISO")
public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
throws Exception {
MultipartFile pdf = request.getFileInput();
@@ -308,7 +308,7 @@ public class CertSignController {
}
doc.saveIncremental(output);
} catch (Exception e) {
logger.error("exception", e);
log.error("exception", e);
}
}

View File

@@ -56,8 +56,6 @@ import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.xml.DomXmpParser;
import org.apache.xmpbox.xml.XmpParsingException;
import org.apache.xmpbox.xml.XmpSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -73,16 +71,16 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
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.PDFFile;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/security")
@Slf4j
@Tag(name = "Security", description = "Security APIs")
public class GetInfoOnPDF {
private static final Logger logger = LoggerFactory.getLogger(GetInfoOnPDF.class);
static ObjectMapper objectMapper = new ObjectMapper();
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
@@ -224,7 +222,7 @@ public class GetInfoOnPDF {
javascriptArray.add(jsNode);
}
} catch (IOException e) {
logger.error("exception", e);
log.error("exception", e);
}
}
}
@@ -257,7 +255,7 @@ public class GetInfoOnPDF {
}
} catch (Exception e) {
// TODO Auto-generated catch block
logger.error("exception", e);
log.error("exception", e);
}
boolean isPdfACompliant = checkForStandard(pdfBoxDoc, "PDF/A");
@@ -309,7 +307,7 @@ public class GetInfoOnPDF {
new XmpSerializer().serialize(xmpMeta, os, true);
xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8);
} catch (XmpParsingException | IOException e) {
logger.error("exception", e);
log.error("exception", e);
}
}
@@ -585,7 +583,7 @@ public class GetInfoOnPDF {
MediaType.APPLICATION_JSON);
} catch (Exception e) {
logger.error("exception", e);
log.error("exception", e);
}
return null;
}
@@ -595,7 +593,9 @@ public class GetInfoOnPDF {
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
permissionsNode.put("Extracting for accessibility", getPermissionState(ap.canExtractForAccessibility()));
permissionsNode.put(
"Extracting for accessibility",
getPermissionState(ap.canExtractForAccessibility()));
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
@@ -699,7 +699,7 @@ public class GetInfoOnPDF {
Exception
e) { // Catching general exception for brevity, ideally you'd catch specific
// exceptions.
logger.error("exception", e);
log.error("exception", e);
}
return false;

View File

@@ -5,8 +5,6 @@ import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
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;
@@ -29,8 +27,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Security", description = "Security APIs")
public class PasswordController {
private static final Logger logger = LoggerFactory.getLogger(PasswordController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -42,7 +38,8 @@ public class PasswordController {
@Operation(
summary = "Remove password from a PDF file",
description =
"This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
"This endpoint removes the password from a protected PDF file. Users need to provide the"
+ " existing password. Input:PDF Output:PDF Type:SISO")
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
throws IOException {
MultipartFile fileInput = request.getFileInput();
@@ -60,7 +57,8 @@ public class PasswordController {
@Operation(
summary = "Add password to a PDF file",
description =
"This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
"This endpoint adds password protection to a PDF file. Users can specify a set of"
+ " permissions that should be applied to the file. Input:PDF Output:PDF")
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
throws IOException {
MultipartFile fileInput = request.getFileInput();

View File

@@ -8,8 +8,6 @@ import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
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;
@@ -22,6 +20,7 @@ import io.github.pixee.security.Filenames;
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.PDFText;
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
import stirling.software.SPDF.pdf.TextFinder;
@@ -31,11 +30,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/security")
@Slf4j
@Tag(name = "Security", description = "Security APIs")
public class RedactController {
private static final Logger logger = LoggerFactory.getLogger(RedactController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -47,7 +45,8 @@ public class RedactController {
@Operation(
summary = "Redacts listOfText in a PDF document",
description =
"This operation takes an input PDF file and redacts the provided listOfText. Input:PDF, Output:PDF, Type:SISO")
"This operation takes an input PDF file and redacts the provided listOfText. Input:PDF,"
+ " Output:PDF, Type:SISO")
public ResponseEntity<byte[]> redactPdf(@ModelAttribute RedactPdfRequest request)
throws Exception {
MultipartFile file = request.getFileInput();
@@ -68,7 +67,7 @@ public class RedactController {
}
redactColor = Color.decode(colorString);
} catch (NumberFormatException e) {
logger.warn("Invalid color string provided. Using default color BLACK for redaction.");
log.warn("Invalid color string provided. Using default color BLACK for redaction.");
redactColor = Color.BLACK;
}

View File

@@ -8,8 +8,6 @@ import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
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;
@@ -31,8 +29,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Security", description = "Security APIs")
public class RemoveCertSignController {
private static final Logger logger = LoggerFactory.getLogger(RemoveCertSignController.class);
private final CustomPDDocumentFactory pdfDocumentFactory;
@Autowired
@@ -44,7 +40,8 @@ public class RemoveCertSignController {
@Operation(
summary = "Remove digital signature from PDF",
description =
"This endpoint accepts a PDF file and returns the PDF file without the digital signature. Input: PDF, Output: PDF")
"This endpoint accepts a PDF file and returns the PDF file without the digital signature."
+ " Input: PDF, Output: PDF")
public ResponseEntity<byte[]> removeCertSignPDF(@ModelAttribute PDFFile request)
throws Exception {
MultipartFile pdf = request.getFileInput();

View File

@@ -92,20 +92,29 @@ public class ValidateSignatureController {
SignerInformationStore signerStore = signedData.getSignerInfos();
for (SignerInformation signer : signerStore.getSigners()) {
X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next();
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
X509CertificateHolder certHolder =
(X509CertificateHolder)
certStore.getMatches(signer.getSID()).iterator().next();
X509Certificate cert =
new JcaX509CertificateConverter().getCertificate(certHolder);
boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
boolean isValid =
signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
result.setValid(isValid);
// Additional validations
result.setChainValid(customCert != null
? certValidationService.validateCertificateChainWithCustomCert(cert, customCert)
: certValidationService.validateCertificateChain(cert));
result.setChainValid(
customCert != null
? certValidationService
.validateCertificateChainWithCustomCert(
cert, customCert)
: certValidationService.validateCertificateChain(cert));
result.setTrustValid(customCert != null
? certValidationService.validateTrustWithCustomCert(cert, customCert)
: certValidationService.validateTrustStore(cert));
result.setTrustValid(
customCert != null
? certValidationService.validateTrustWithCustomCert(
cert, customCert)
: certValidationService.validateTrustStore(cert));
result.setNotRevoked(!certValidationService.isRevoked(cert));
result.setNotExpired(!cert.getNotAfter().before(new Date()));
@@ -123,17 +132,18 @@ public class ValidateSignatureController {
result.setValidFrom(cert.getNotBefore().toString());
result.setValidUntil(cert.getNotAfter().toString());
result.setSignatureAlgorithm(cert.getSigAlgName());
// Get key size (if possible)
try {
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
result.setKeySize(
((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
} catch (Exception e) {
// If not RSA or error, set to 0
result.setKeySize(0);
}
result.setVersion(String.valueOf(cert.getVersion()));
// Set key usage
List<String> keyUsages = new ArrayList<>();
boolean[] keyUsageFlags = cert.getKeyUsage();
@@ -150,9 +160,11 @@ public class ValidateSignatureController {
}
}
result.setKeyUsages(keyUsages);
// Check if self-signed
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
result.setSelfSigned(
cert.getSubjectX500Principal()
.equals(cert.getIssuerX500Principal()));
}
} catch (Exception e) {
result.setValid(false);

View File

@@ -15,8 +15,6 @@ import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
@@ -31,16 +29,16 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.SignatureFile;
import stirling.software.SPDF.service.SignatureService;
@Controller
@Tag(name = "General", description = "General APIs")
@Slf4j
public class GeneralWebController {
private static final Logger logger = LoggerFactory.getLogger(GeneralWebController.class);
@GetMapping("/pipeline")
@Hidden
public String pipelineForm(Model model) {
@@ -82,7 +80,7 @@ public class GeneralWebController {
}
} catch (IOException e) {
logger.error("exception", e);
log.error("exception", e);
}
}
if (pipelineConfigsWithNames.size() == 0) {

View File

@@ -6,8 +6,6 @@ import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@@ -22,14 +20,14 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.Dependency;
@Controller
@Slf4j
public class HomeWebController {
private static final Logger logger = LoggerFactory.getLogger(HomeWebController.class);
@GetMapping("/about")
@Hidden
public String gameForm(Model model) {
@@ -50,7 +48,7 @@ public class HomeWebController {
mapper.readValue(json, new TypeReference<Map<String, List<Dependency>>>() {});
model.addAttribute("dependencies", data.get("dependencies"));
} catch (IOException e) {
logger.error("exception", e);
log.error("exception", e);
}
return "licenses";
}

View File

@@ -73,6 +73,7 @@ public class ApplicationProperties {
private int loginAttemptCount;
private long loginResetTimeMinutes;
private String loginMethod = "all";
private String customGlobalAPIKey;
public Boolean isAltLogin() {
return saml2.getEnabled() || oauth2.getEnabled();
@@ -121,18 +122,19 @@ public class ApplicationProperties {
@Getter
@Setter
@ToString
public static class SAML2 {
private Boolean enabled = false;
private Boolean autoCreateUser = false;
private Boolean blockRegistration = false;
private String registrationId = "stirling";
private String idpMetadataUri;
@ToString.Exclude private String idpMetadataUri;
private String idpSingleLogoutUrl;
private String idpSingleLoginUrl;
private String idpIssuer;
private String idpCert;
private String privateKey;
private String spCert;
@ToString.Exclude private String privateKey;
@ToString.Exclude private String spCert;
public InputStream getIdpMetadataUri() throws IOException {
if (idpMetadataUri.startsWith("classpath:")) {
@@ -285,6 +287,7 @@ public class ApplicationProperties {
public static class AutomaticallyGenerated {
@ToString.Exclude private String key;
private String UUID;
private String appVersion;
}
@Data

View File

@@ -5,8 +5,6 @@ import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -14,18 +12,20 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.utils.GeneralUtils;
@Data
@NoArgsConstructor
@Slf4j
@EqualsAndHashCode(callSuper = true)
public class PDFWithPageNums extends PDFFile {
private static final Logger logger = LoggerFactory.getLogger(PDFWithPageNums.class);
@Schema(
description =
"The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a constant (e.g., '2n+1', '3n', '6n-5')\"")
"The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the"
+ " format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a"
+ " constant (e.g., '2n+1', '3n', '6n-5')\"")
private String pageNumbers;
@Hidden
@@ -35,7 +35,7 @@ public class PDFWithPageNums extends PDFFile {
pageCount = Loader.loadPDF(getFileInput().getBytes()).getNumberOfPages();
} catch (IOException e) {
// TODO Auto-generated catch block
logger.error("exception", e);
log.error("exception", e);
}
return GeneralUtils.parsePageList(pageNumbers, pageCount, zeroCount);
}

View File

@@ -16,16 +16,15 @@ public class SignatureValidationResult {
private boolean trustValid;
private boolean notExpired;
private boolean notRevoked;
private String issuerDN; // Certificate issuer's Distinguished Name
private String subjectDN; // Certificate subject's Distinguished Name
private String serialNumber; // Certificate serial number
private String validFrom; // Certificate validity start date
private String validUntil; // Certificate validity end date
private String signatureAlgorithm;// Algorithm used for signing
private int keySize; // Key size in bits
private String version; // Certificate version
private List<String> keyUsages; // List of key usage purposes
private boolean isSelfSigned; // Whether the certificate is self-signed
private String issuerDN; // Certificate issuer's Distinguished Name
private String subjectDN; // Certificate subject's Distinguished Name
private String serialNumber; // Certificate serial number
private String validFrom; // Certificate validity start date
private String validUntil; // Certificate validity end date
private String signatureAlgorithm; // Algorithm used for signing
private int keySize; // Key size in bits
private String version; // Certificate version
private List<String> keyUsages; // List of key usage purposes
private boolean isSelfSigned; // Whether the certificate is self-signed
}

View File

@@ -1,6 +1,5 @@
package stirling.software.SPDF.service;
import io.github.pixee.security.BoundedLineReader;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -24,6 +23,8 @@ import java.util.Set;
import org.springframework.stereotype.Service;
import io.github.pixee.security.BoundedLineReader;
import jakarta.annotation.PostConstruct;
@Service

View File

@@ -7,20 +7,18 @@ import java.io.InputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PdfMetadata;
import stirling.software.SPDF.model.api.PDFFile;
@Component
@Slf4j
public class CustomPDDocumentFactory {
private static final Logger logger = LoggerFactory.getLogger(CustomPDDocumentFactory.class);
private final PdfMetadataService pdfMetadataService;
@Autowired
@@ -133,10 +131,10 @@ public class CustomPDDocumentFactory {
private PDDocument removezeropassword(PDDocument document) throws IOException {
if (document.isEncrypted()) {
try {
logger.info("Removing security from the source document");
log.info("Removing security from the source document");
document.setAllSecurityToBeRemoved(true);
} catch (Exception e) {
logger.warn("Cannot decrypt the pdf");
log.warn("Cannot decrypt the pdf");
}
}
return document;

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