Merge branch 'main' into bug/csrf-decryption-API

This commit is contained in:
Anthony Stirling 2024-12-13 20:25:52 +00:00 committed by GitHub
commit 1f39481efe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
75 changed files with 2309 additions and 843 deletions

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

@ -0,0 +1,91 @@
name: Test Installers Build
on:
workflow_dispatch:
release:
types: [created]
permissions:
contents: write
packages: write
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 }}
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: "21"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
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-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ 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@v4
with:
name: Stirling-PDF-${{ matrix.platform }}-installer.{{ matrix.ext }}
path: Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}
retention-days: 1
if-no-files-found: error

View File

@ -35,36 +35,37 @@ jobs:
run: ./gradlew clean createExe run: ./gradlew clean createExe
env: env:
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }} DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
STIRLING_PDF_DESKTOP_UI: false
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Rename binarie - name: Rename binarie
if: matrix.file_suffix != '' run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
- name: Upload Assets binarie - name: Upload Assets binarie
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
path: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe path: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
name: Stirling-PDF${{ matrix.file_suffix }}.exe name: Stirling-PDF-Server${{ matrix.file_suffix }}.exe
overwrite: true overwrite: true
retention-days: 1 retention-days: 1
if-no-files-found: error if-no-files-found: error
- name: Upload binaries to release - name: Upload binaries to release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe files: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
- name: Rename jar binaries - name: Rename jar binaries
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
- name: Upload Assets jar binaries - name: Upload Assets jar binaries
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
path: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar path: ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
name: Stirling-PDF${{ matrix.file_suffix }}.jar name: Stirling-PDF-Server${{ matrix.file_suffix }}.jar
overwrite: true overwrite: true
retention-days: 1 retention-days: 1
if-no-files-found: error if-no-files-found: error
@ -72,4 +73,44 @@ jobs:
- name: Upload jar binaries to release - name: Upload jar binaries to release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar files: ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
push-ui:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: 8.7
- name: Generate exe
run: ./gradlew clean createExe
env:
DOCKER_ENABLE_SECURITY: false
STIRLING_PDF_DESKTOP_UI: true
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Upload Assets binarie
uses: actions/upload-artifact@v4
with:
path: ./build/launch4j/Stirling-PDF.exe
name: Stirling-PDF.exe
overwrite: true
retention-days: 1
if-no-files-found: error
- name: Upload binaries to release
uses: softprops/action-gh-release@v2
with:
files: ./build/launch4j/Stirling-PDF.exe

1
.gitignore vendored
View File

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

View File

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

View File

@ -191,44 +191,44 @@ Stirling-PDF currently supports 38 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![95%](https://geps.dev/progress/95) | | Arabic (العربية) (ar_AR) | ![94%](https://geps.dev/progress/94) |
| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![93%](https://geps.dev/progress/93) | | Azerbaijani (Azərbaycan Dili) (az_AZ) | ![93%](https://geps.dev/progress/93) |
| Basque (Euskara) (eu_ES) | ![54%](https://geps.dev/progress/54) | | Basque (Euskara) (eu_ES) | ![53%](https://geps.dev/progress/53) |
| Bulgarian (Български) (bg_BG) | ![90%](https://geps.dev/progress/90) | | Bulgarian (Български) (bg_BG) | ![90%](https://geps.dev/progress/90) |
| Catalan (Català) (ca_CA) | ![85%](https://geps.dev/progress/85) | | Catalan (Català) (ca_CA) | ![84%](https://geps.dev/progress/84) |
| Croatian (Hrvatski) (hr_HR) | ![92%](https://geps.dev/progress/92) | | Croatian (Hrvatski) (hr_HR) | ![91%](https://geps.dev/progress/91) |
| Czech (Česky) (cs_CZ) | ![91%](https://geps.dev/progress/91) | | Czech (Česky) (cs_CZ) | ![91%](https://geps.dev/progress/91) |
| Danish (Dansk) (da_DK) | ![90%](https://geps.dev/progress/90) | | Danish (Dansk) (da_DK) | ![90%](https://geps.dev/progress/90) |
| Dutch (Nederlands) (nl_NL) | ![90%](https://geps.dev/progress/90) | | Dutch (Nederlands) (nl_NL) | ![89%](https://geps.dev/progress/89) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![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) | | German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| Greek (Ελληνικά) (el_GR) | ![91%](https://geps.dev/progress/91) | | Greek (Ελληνικά) (el_GR) | ![90%](https://geps.dev/progress/90) |
| Hindi (हिंदी) (hi_IN) | ![89%](https://geps.dev/progress/89) | | Hindi (हिंदी) (hi_IN) | ![88%](https://geps.dev/progress/88) |
| Hungarian (Magyar) (hu_HU) | ![92%](https://geps.dev/progress/92) | | Hungarian (Magyar) (hu_HU) | ![91%](https://geps.dev/progress/91) |
| Indonesian (Bahasa Indonesia) (id_ID) | ![91%](https://geps.dev/progress/91) | | Indonesian (Bahasa Indonesia) (id_ID) | ![91%](https://geps.dev/progress/91) |
| Irish (Gaeilge) (ga_IE) | ![83%](https://geps.dev/progress/83) | | Irish (Gaeilge) (ga_IE) | ![83%](https://geps.dev/progress/83) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) |
| Japanese (日本語) (ja_JP) | ![81%](https://geps.dev/progress/81) | | Japanese (日本語) (ja_JP) | ![81%](https://geps.dev/progress/81) |
| Korean (한국어) (ko_KR) | ![90%](https://geps.dev/progress/90) | | Korean (한국어) (ko_KR) | ![89%](https://geps.dev/progress/89) |
| Norwegian (Norsk) (no_NB) | ![83%](https://geps.dev/progress/83) | | Norwegian (Norsk) (no_NB) | ![82%](https://geps.dev/progress/82) |
| Persian (فارسی) (fa_IR) | ![100%](https://geps.dev/progress/100) | | Persian (فارسی) (fa_IR) | ![99%](https://geps.dev/progress/99) |
| Polish (Polski) (pl_PL) | ![91%](https://geps.dev/progress/91) | | Polish (Polski) (pl_PL) | ![90%](https://geps.dev/progress/90) |
| Portuguese (Português) (pt_PT) | ![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) | | Portuguese Brazilian (Português) (pt_BR) | ![98%](https://geps.dev/progress/98) |
| Romanian (Română) (ro_RO) | ![85%](https://geps.dev/progress/85) | | Romanian (Română) (ro_RO) | ![85%](https://geps.dev/progress/85) |
| Russian (Русский) (ru_RU) | ![91%](https://geps.dev/progress/91) | | Russian (Русский) (ru_RU) | ![90%](https://geps.dev/progress/90) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![67%](https://geps.dev/progress/67) | | 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) | ![86%](https://geps.dev/progress/86) |
| Slovakian (Slovensky) (sk_SK) | ![78%](https://geps.dev/progress/78) | | Slovakian (Slovensky) (sk_SK) | ![78%](https://geps.dev/progress/78) |
| Spanish (Español) (es_ES) | ![92%](https://geps.dev/progress/92) | | Spanish (Español) (es_ES) | ![91%](https://geps.dev/progress/91) |
| Swedish (Svenska) (sv_SE) | ![91%](https://geps.dev/progress/91) | | Swedish (Svenska) (sv_SE) | ![90%](https://geps.dev/progress/90) |
| Thai (ไทย) (th_TH) | ![91%](https://geps.dev/progress/91) | | Thai (ไทย) (th_TH) | ![90%](https://geps.dev/progress/90) |
| Traditional Chinese (繁體中文) (zh_TW) | ![92%](https://geps.dev/progress/92) | | Traditional Chinese (繁體中文) (zh_TW) | ![91%](https://geps.dev/progress/91) |
| Turkish (Türkçe) (tr_TR) | ![87%](https://geps.dev/progress/87) | | Turkish (Türkçe) (tr_TR) | ![86%](https://geps.dev/progress/86) |
| Ukrainian (Українська) (uk_UA) | ![76%](https://geps.dev/progress/76) | | 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.) ## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
@ -405,7 +405,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. 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 ## FAQ

View File

@ -8,6 +8,7 @@ plugins {
id "com.diffplug.spotless" version "6.25.0" id "com.diffplug.spotless" version "6.25.0"
id "com.github.jk1.dependency-license-report" version "2.9" id "com.github.jk1.dependency-license-report" version "2.9"
//id "nebula.lint" version "19.0.3" //id "nebula.lint" version "19.0.3"
id("org.panteleyev.jpackageplugin") version "1.6.0"
} }
@ -26,7 +27,7 @@ ext {
} }
group = "stirling.software" group = "stirling.software"
version = "0.36.0" version = "0.36.1"
java { java {
@ -41,6 +42,9 @@ repositories {
maven { maven {
url 'https://build.shibboleth.net/maven/releases' 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 { licenseReport {
@ -64,6 +68,12 @@ sourceSets {
exclude "stirling/software/SPDF/model/User.java" exclude "stirling/software/SPDF/model/User.java"
exclude "stirling/software/SPDF/repository/**" exclude "stirling/software/SPDF/repository/**"
} }
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
exclude "stirling/software/SPDF/UI/impl/**"
}
} }
} }
} }
@ -74,16 +84,153 @@ openApi {
outputFileName = "SwaggerDoc.json" 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 { launch4j {
icon = "${projectDir}/src/main/resources/static/favicon.ico" icon = "${projectDir}/src/main/resources/static/favicon.ico"
outfile="Stirling-PDF.exe" outfile="Stirling-PDF.exe"
headerType="console"
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
headerType = "gui"
} else {
headerType = "console"
}
jarTask = tasks.bootJar jarTask = tasks.bootJar
errTitle="Encountered error, Do you have Java 21?" errTitle="Encountered error, Do you have Java 21?"
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe" 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" jreMinVersion="17"
mutexName="Stirling-PDF" mutexName="Stirling-PDF"
@ -123,6 +270,13 @@ configurations.all {
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
} }
dependencies { dependencies {
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 //security updates
implementation "org.springframework:spring-webmvc:6.2.0" implementation "org.springframework:spring-webmvc:6.2.0"
@ -142,7 +296,7 @@ dependencies {
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") { if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion" 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-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
@ -271,7 +425,14 @@ jar {
tasks.named("test") { tasks.named("test") {
useJUnitPlatform() useJUnitPlatform()
} }
task printVersion { 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 import re
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
API_HEADERS = {
'X-API-KEY': '123456789'
}
######### #########
# GIVEN # # GIVEN #
######### #########
@ -227,7 +231,7 @@ def save_generated_pdf(context, filename):
def step_send_get_request(context, endpoint): def step_send_get_request(context, endpoint):
base_url = "http://localhost:8080" base_url = "http://localhost:8080"
full_url = f"{base_url}{endpoint}" full_url = f"{base_url}{endpoint}"
response = requests.get(full_url) response = requests.get(full_url, headers=API_HEADERS)
context.response = response context.response = response
@when('I send a GET request to "{endpoint}" with parameters') @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" base_url = "http://localhost:8080"
params = {row['parameter']: row['value'] for row in context.table} params = {row['parameter']: row['value'] for row in context.table}
full_url = f"{base_url}{endpoint}" 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 context.response = response
@when('I send the API request to the endpoint "{endpoint}"') @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}") print(f"form_data {file.name} with {mime_type}")
form_data.append((key, (file.name, file, 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 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

@ -77,6 +77,11 @@ ignore = [
'language.direction', 'language.direction',
] ]
[fa_IR]
ignore = [
'language.direction',
]
[fr_FR] [fr_FR]
ignore = [ ignore = [
'AddStampRequest.alphabet', 'AddStampRequest.alphabet',

View File

@ -28,7 +28,7 @@ public class LicenseKeyChecker {
this.checkLicense(); this.checkLicense();
} }
@Scheduled(initialDelay = 604800000,fixedRate = 604800000) // 7 days in milliseconds @Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds
public void checkLicensePeriodically() { public void checkLicensePeriodically() {
checkLicense(); checkLicense();
} }

View File

@ -1,5 +1,6 @@
package stirling.software.SPDF; package stirling.software.SPDF;
import java.awt.*;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.nio.file.Files; import java.nio.file.Files;
@ -8,6 +9,9 @@ import java.nio.file.Paths;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import javax.swing.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -19,13 +23,16 @@ import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import io.github.pixee.security.SystemCommand; import io.github.pixee.security.SystemCommand;
import jakarta.annotation.PostConstruct; 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.config.ConfigInitializer;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@SpringBootApplication @SpringBootApplication
@EnableScheduling @EnableScheduling
@Slf4j
public class SPdfApplication { public class SPdfApplication {
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class); private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
@ -67,36 +74,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 { public static void main(String[] args) throws IOException, InterruptedException {
SpringApplication app = new SpringApplication(SPdfApplication.class); 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.setAdditionalProfiles("default");
app.addInitializers(new ConfigInitializer()); app.addInitializers(new ConfigInitializer());
Map<String, String> propertyFiles = new HashMap<>(); Map<String, String> propertyFiles = new HashMap<>();
@ -120,14 +110,20 @@ public class SPdfApplication {
} else { } else {
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist."); logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
} }
Properties finalProps = new Properties();
if (!propertyFiles.isEmpty()) { if (!propertyFiles.isEmpty()) {
app.setDefaultProperties( finalProps.putAll(
Collections.singletonMap( Collections.singletonMap(
"spring.config.additional-location", "spring.config.additional-location",
propertyFiles.get("spring.config.additional-location"))); propertyFiles.get("spring.config.additional-location")));
} }
if (!props.isEmpty()) {
finalProps.putAll(props);
}
app.setDefaultProperties(finalProps);
app.run(args); app.run(args);
// Ensure directories are created // Ensure directories are created
@ -147,6 +143,46 @@ public class SPdfApplication {
logger.info("Navigate to {}", url); logger.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) {
logger.error("Error opening browser: {}", e.getMessage());
}
}
}
logger.info("Running configs {}", applicationProperties.toString());
}
@PreDestroy
public void cleanup() {
if (webBrowser != null) {
webBrowser.cleanup();
}
}
public static String getStaticBaseUrl() { public static String getStaticBaseUrl() {
return baseUrlStatic; return baseUrlStatic;
} }

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

@ -260,6 +260,9 @@ public class EndpointConfiguration {
// Pdftohtml dependent endpoints // Pdftohtml dependent endpoints
addEndpointToGroup("Pdftohtml", "pdf-to-html"); addEndpointToGroup("Pdftohtml", "pdf-to-html");
// disabled for now while we resolve issues
disableEndpoint("pdf-to-pdfa");
} }
private void processEnvironmentConfigs() { private void processEnvironmentConfigs() {

View File

@ -1,11 +1,14 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.IOException; import java.io.IOException;
import java.util.Properties;
import java.util.UUID; import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import io.micrometer.common.util.StringUtils; import io.micrometer.common.util.StringUtils;
@ -23,6 +26,18 @@ public class InitialSetup {
@Autowired private ApplicationProperties applicationProperties; @Autowired private ApplicationProperties applicationProperties;
@PostConstruct @PostConstruct
public void init() throws IOException {
initUUIDKey();
initSecretKey();
initEnableCSRFSecurity();
initLegalUrls();
initSetAppVersion();
}
public void initUUIDKey() throws IOException { public void initUUIDKey() throws IOException {
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID(); String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
if (!GeneralUtils.isValidUUID(uuid)) { if (!GeneralUtils.isValidUUID(uuid)) {
@ -32,7 +47,6 @@ public class InitialSetup {
} }
} }
@PostConstruct
public void initSecretKey() throws IOException { public void initSecretKey() throws IOException {
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
if (!GeneralUtils.isValidUUID(secretKey)) { 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 { public void initLegalUrls() throws IOException {
// Initialize Terms and Conditions // Initialize Terms and Conditions
String termsUrl = applicationProperties.getLegal().getTermsAndConditions(); String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
if (StringUtils.isEmpty(termsUrl)) { if (StringUtils.isEmpty(termsUrl)) {
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions"; String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl); GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false);
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl); applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
} }
@ -56,8 +81,23 @@ public class InitialSetup {
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy(); String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
if (StringUtils.isEmpty(privacyUrl)) { if (StringUtils.isEmpty(privacyUrl)) {
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy"; String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl); GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl, false);
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl); 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

@ -75,5 +75,6 @@ public class InitialSecuritySetup {
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId()); userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
log.info("Internal API user created: " + 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 @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
if (applicationProperties.getSecurity().getCsrfDisabled()) { if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
http.csrf(csrf -> csrf.disable()); http.csrf(csrf -> csrf.disable());
} }
@ -116,7 +116,7 @@ public class SecurityConfiguration {
csrf -> csrf ->
csrf.ignoringRequestMatchers( csrf.ignoringRequestMatchers(
request -> { 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 // If there's no API key, don't ignore CSRF
// (return false) // (return false)
@ -289,17 +289,17 @@ public class SecurityConfiguration {
} }
} else { } else {
if (!applicationProperties.getSecurity().getCsrfDisabled()) { // if (!applicationProperties.getSecurity().getCsrfDisabled()) {
CookieCsrfTokenRepository cookieRepo = // CookieCsrfTokenRepository cookieRepo =
CookieCsrfTokenRepository.withHttpOnlyFalse(); // CookieCsrfTokenRepository.withHttpOnlyFalse();
CsrfTokenRequestAttributeHandler requestHandler = // CsrfTokenRequestAttributeHandler requestHandler =
new CsrfTokenRequestAttributeHandler(); // new CsrfTokenRequestAttributeHandler();
requestHandler.setCsrfRequestAttributeName(null); // requestHandler.setCsrfRequestAttributeName(null);
http.csrf( // http.csrf(
csrf -> // csrf ->
csrf.csrfTokenRepository(cookieRepo) // csrf.csrfTokenRepository(cookieRepo)
.csrfTokenRequestHandler(requestHandler)); // .csrfTokenRequestHandler(requestHandler));
} // }
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); 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 // Check for API key in the request headers if no authentication exists
if (authentication == null || !authentication.isAuthenticated()) { 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()) { if (apiKey != null && !apiKey.trim().isEmpty()) {
try { try {
// Use API key to authenticate. This requires you to have an authentication // 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; String identifier = null;
// Check for API key in the request headers // 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()) { if (apiKey != null && !apiKey.trim().isEmpty()) {
identifier = identifier =
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames "API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
@ -79,7 +79,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
Role userRole = Role userRole =
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication()); getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
if (request.getHeader("X-API-Key") != null) { if (request.getHeader("X-API-KEY") != null) {
// It's an API call // It's an API call
processRequest( processRequest(
userRole.getApiCallsPerDay(), 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 @Override
public long getTotalUsersCount() { public long getTotalUsersCount() {
return userRepository.count(); return userRepository.count();

View File

@ -52,84 +52,115 @@ 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") "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) public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
throws IOException { throws IOException {
MultipartFile file = request.getFileInput();
String pages = request.getPageNumbers();
// open the pdf document
PDDocument document = Loader.loadPDF(file.getBytes()); PDDocument document = null;
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document); Path zipFile = null;
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
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>(); List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
int previousPageNumber = 0;
for (int splitPoint : pageNumbers) { try {
try (PDDocument splitDocument =
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) { MultipartFile file = request.getFileInput();
for (int i = previousPageNumber; i <= splitPoint; i++) { String pages = request.getPageNumbers();
PDPage page = document.getPage(i); // open the pdf document
splitDocument.addPage(page);
logger.info("Adding page {} to split document", i); 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
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);
}
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) {
logger.error("Failed splitting documents and saving them", e);
throw e;
} }
previousPageNumber = splitPoint + 1; }
// Transfer metadata to split pdf // closing the original document
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata); document.close();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); zipFile = Files.createTempFile("split_documents", ".zip");
splitDocument.save(baos);
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();
logger.info("Wrote split document {} to zip file", fileName);
}
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed splitting documents and saving them", e); logger.error("Failed writing to zip", e);
throw e; throw e;
} }
}
// closing the original document logger.info(
document.close(); "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 = } finally {
Filenames.toSimpleFileName(file.getOriginalFilename()) try {
.replaceFirst("[.][^.]+$", ""); // Close the main document
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) { if (document != null) {
// loop through the split documents and write them to the zip file document.close();
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 // Close all ByteArrayOutputStreams
ZipEntry pdfEntry = new ZipEntry(fileName); for (ByteArrayOutputStream baos : splitDocumentsBoas) {
zipOut.putNextEntry(pdfEntry); if (baos != null) {
zipOut.write(pdf); baos.close();
zipOut.closeEntry(); }
}
logger.info("Wrote split document {} to zip file", fileName); // Delete temporary zip file
if (zipFile != null) {
Files.deleteIfExists(zipFile);
}
} catch (Exception e) {
logger.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

@ -59,70 +59,86 @@ public class SplitPdfByChaptersController {
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request) public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
throws Exception { throws Exception {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
boolean includeMetadata = request.getIncludeMetadata(); PDDocument sourceDocument = null;
Integer bookmarkLevel = Path zipFile = null;
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());
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 { try {
bookmarks = boolean includeMetadata = request.getIncludeMetadata();
extractOutlineItems( Integer bookmarkLevel =
sourceDocument, request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
outline.getFirstChild(), if (bookmarkLevel < 0) {
bookmarks, return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
outline.getFirstChild().getNextSibling(), }
0, sourceDocument = Loader.loadPDF(file.getBytes());
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) { PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
logger.error("Unable to extract outline items", e);
return ResponseEntity.internalServerError() if (outline == null) {
.body("Unable to extract outline items".getBytes()); 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);
} catch (Exception e) {
logger.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) {
logger.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) {
logger.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) { private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {

View File

@ -105,15 +105,13 @@ public class SplitPdfBySectionsController {
if (sectionNum == horiz * verti) pageNum++; if (sectionNum == horiz * verti) pageNum++;
} }
} catch (Exception e) {
logger.error("exception", e);
} finally {
data = Files.readAllBytes(zipFile); data = Files.readAllBytes(zipFile);
return WebResponseUtils.bytesToWebResponse(
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
} finally {
Files.deleteIfExists(zipFile); Files.deleteIfExists(zipFile);
} }
return WebResponseUtils.bytesToWebResponse(
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
} }
public List<PDDocument> splitPdfPages( public List<PDDocument> splitPdfPages(

View File

@ -65,112 +65,137 @@ public class ConvertImgPDFController {
String colorType = request.getColorType(); String colorType = request.getColorType();
String dpi = request.getDpi(); String dpi = request.getDpi();
byte[] pdfBytes = file.getBytes(); Path tempFile = null;
ImageType colorTypeResult = ImageType.RGB; Path tempOutputDir = null;
if ("greyscale".equals(colorType)) { Path tempPdfPath = null;
colorTypeResult = ImageType.GRAY;
} else if ("blackwhite".equals(colorType)) {
colorTypeResult = ImageType.BINARY;
}
// returns bytes for image
boolean singleImage = "single".equals(singleOrMultiple);
byte[] result = null; byte[] result = null;
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
result = try {
PdfUtils.convertFromPdf( byte[] pdfBytes = file.getBytes();
pdfBytes, ImageType colorTypeResult = ImageType.RGB;
"webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(), if ("greyscale".equals(colorType)) {
colorTypeResult, colorTypeResult = ImageType.GRAY;
singleImage, } else if ("blackwhite".equals(colorType)) {
Integer.valueOf(dpi), colorTypeResult = ImageType.BINARY;
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();
} }
// returns bytes for image
boolean singleImage = "single".equals(singleOrMultiple);
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand(); result =
PdfUtils.convertFromPdf(
List<String> command = new ArrayList<>(); pdfBytes,
command.add(pythonVersion); "webp".equalsIgnoreCase(imageFormat)
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion ? "png"
: imageFormat.toUpperCase(),
// Create a temporary directory for the output WebP files colorTypeResult,
Path tempOutputDir = Files.createTempDirectory("webp_output"); singleImage,
if (singleImage) { Integer.valueOf(dpi),
// Run the Python script to convert PNG to WebP filename);
command.add(tempFile.toString()); if (result == null || result.length == 0) {
command.add(tempOutputDir.toString()); logger.error("resultant bytes for {} is null, error converting ", filename);
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());
} }
command.add("--dpi"); if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
command.add(dpi); throw new IOException("Python is not installed. Required for WebP conversion.");
ProcessExecutorResult resultProcess = } else if ("webp".equalsIgnoreCase(imageFormat)
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV) && CheckProgramInstall.isPythonAvailable()) {
.runCommandWithOutputHandling(command); // Write the output stream to a temp file
tempFile = Files.createTempFile("temp_png", ".png");
// Find all WebP files in the output directory try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
List<Path> webpFiles = fos.write(result);
Files.walk(tempOutputDir) fos.flush();
.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();
}
} }
bodyBytes = zipOutputStream.toByteArray();
}
// Clean up the temporary files
Files.deleteIfExists(tempFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
result = bodyBytes;
}
if (singleImage) { String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
String docName = filename + "." + imageFormat;
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat)); List<String> command = new ArrayList<>();
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType); command.add(pythonVersion);
} else { command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
String zipFilename = filename + "_convertedToImages.zip";
return WebResponseUtils.bytesToWebResponse( // Create a temporary directory for the output WebP files
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM); 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()) {
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();
}
}
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) {
logger.error("Error cleaning up temporary files", e);
}
} }
} }

View File

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

View File

@ -221,7 +221,7 @@ public class PipelineProcessor {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
String apiKey = getApiKeyForUser(); String apiKey = getApiKeyForUser();
headers.add("X-API-Key", apiKey); headers.add("X-API-KEY", apiKey);
headers.setContentType(MediaType.MULTIPART_FORM_DATA); headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// Create HttpEntity with the body and headers // Create HttpEntity with the body and headers

View File

@ -595,7 +595,9 @@ public class GetInfoOnPDF {
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument())); permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent())); 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("Form Filling", getPermissionState(ap.canFillInForm()));
permissionsNode.put("Modifying", getPermissionState(ap.canModify())); permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations())); permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));

View File

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

View File

@ -73,6 +73,7 @@ public class ApplicationProperties {
private int loginAttemptCount; private int loginAttemptCount;
private long loginResetTimeMinutes; private long loginResetTimeMinutes;
private String loginMethod = "all"; private String loginMethod = "all";
private String customGlobalAPIKey;
public Boolean isAltLogin() { public Boolean isAltLogin() {
return saml2.getEnabled() || oauth2.getEnabled(); return saml2.getEnabled() || oauth2.getEnabled();
@ -285,6 +286,7 @@ public class ApplicationProperties {
public static class AutomaticallyGenerated { public static class AutomaticallyGenerated {
@ToString.Exclude private String key; @ToString.Exclude private String key;
private String UUID; private String UUID;
private String appVersion;
} }
@Data @Data

View File

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

View File

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

View File

@ -88,15 +88,45 @@ public class GeneralUtils {
public static boolean isURLReachable(String urlStr) { public static boolean isURLReachable(String urlStr) {
try { try {
// Parse the URL
URL url = URI.create(urlStr).toURL(); URL url = URI.create(urlStr).toURL();
// Allow only http and https protocols
String protocol = url.getProtocol();
if (!protocol.equals("http") && !protocol.equals("https")) {
return false; // Disallow other protocols
}
// Check if the host is a local address
String host = url.getHost();
if (isLocalAddress(host)) {
return false; // Exclude local addresses
}
// Check if the URL is reachable
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD"); connection.setRequestMethod("HEAD");
// connection.setConnectTimeout(5000); // Set connection timeout
// connection.setReadTimeout(5000); // Set read timeout
int responseCode = connection.getResponseCode(); int responseCode = connection.getResponseCode();
return (200 <= responseCode && responseCode <= 399); return (200 <= responseCode && responseCode <= 399);
} catch (MalformedURLException e) { } catch (Exception e) {
return false; return false; // Return false in case of any exception
} catch (IOException e) { }
return false; }
private static boolean isLocalAddress(String host) {
try {
// Resolve DNS to IP address
InetAddress address = InetAddress.getByName(host);
// Check for local addresses
return address.isAnyLocalAddress() || // Matches 0.0.0.0 or similar
address.isLoopbackAddress() || // Matches 127.0.0.1 or ::1
address.isSiteLocalAddress() || // Matches private IPv4 ranges: 192.168.x.x, 10.x.x.x, 172.16.x.x to 172.31.x.x
address.getHostAddress().startsWith("fe80:"); // Matches link-local IPv6 addresses
} catch (Exception e) {
return false; // Return false for invalid or unresolved addresses
} }
} }
@ -289,6 +319,10 @@ public class GeneralUtils {
saveKeyToConfig(id, key, true); saveKeyToConfig(id, key, true);
} }
public static void saveKeyToConfig(String id, boolean key) throws IOException {
saveKeyToConfig(id, key, true);
}
public static void saveKeyToConfig(String id, String key, boolean autoGenerated) public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
throws IOException { throws IOException {
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
@ -307,6 +341,24 @@ public class GeneralUtils {
settingsYml.save(); settingsYml.save();
} }
public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated)
throws IOException {
Path path = Paths.get("configs", "settings.yml");
final YamlFile settingsYml = new YamlFile(path.toFile());
DumperOptions yamlOptionssettingsYml =
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
yamlOptionssettingsYml.setSplitLines(false);
settingsYml.loadWithComments();
YamlFileWrapper writer = settingsYml.path(id).set(key);
if (autoGenerated) {
writer.comment("# Automatically Generated Settings (Do Not Edit Directly)");
}
settingsYml.save();
}
public static String generateMachineFingerprint() { public static String generateMachineFingerprint() {
try { try {
// Get the MAC address // Get the MAC address
@ -349,4 +401,33 @@ public class GeneralUtils {
return "GenericID"; return "GenericID";
} }
} }
public static boolean isVersionHigher(String currentVersion, String compareVersion) {
if (currentVersion == null || compareVersion == null) {
return false;
}
// Split versions into components
String[] current = currentVersion.split("\\.");
String[] compare = compareVersion.split("\\.");
// Get the length of the shorter version array
int length = Math.min(current.length, compare.length);
// Compare each component
for (int i = 0; i < length; i++) {
int currentPart = Integer.parseInt(current[i]);
int comparePart = Integer.parseInt(compare[i]);
if (currentPart > comparePart) {
return true;
}
if (currentPart < comparePart) {
return false;
}
}
// If all components so far are equal, the longer version is considered higher
return current.length > compare.length;
}
} }

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=الصفحات المحددة
multiTool.undo=تراجع multiTool.undo=تراجع
multiTool.redo=إعادة إجراء multiTool.redo=إعادة إجراء
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية! multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Seçilmiş Səhifə(lər)
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin! multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Ausgewählte Seite(n)
multiTool.undo=Rückgängig machen multiTool.undo=Rückgängig machen
multiTool.redo=Wiederherstellen multiTool.redo=Wiederherstellen
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen! multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -122,6 +122,7 @@ enterpriseEdition.warning=این ویژگی فقط برای کاربران حر
enterpriseEdition.yamlAdvert=Stirling PDF Pro از فایل‌های پیکربندی YAML و دیگر ویژگی‌های SSO پشتیبانی می‌کند. enterpriseEdition.yamlAdvert=Stirling PDF Pro از فایل‌های پیکربندی YAML و دیگر ویژگی‌های SSO پشتیبانی می‌کند.
enterpriseEdition.ssoAdvert=به دنبال ویژگی‌های بیشتر برای مدیریت کاربران هستید؟ Stirling PDF Pro را بررسی کنید enterpriseEdition.ssoAdvert=به دنبال ویژگی‌های بیشتر برای مدیریت کاربران هستید؟ Stirling PDF Pro را بررسی کنید
################# #################
# Analytics # # Analytics #
################# #################
@ -515,6 +516,7 @@ home.validateSignature.title=اعتبارسنجی امضای PDF
home.validateSignature.desc=تأیید امضاها و گواهی‌های دیجیتال در اسناد PDF home.validateSignature.desc=تأیید امضاها و گواهی‌های دیجیتال در اسناد PDF
validateSignature.tags=امضا، تأیید، اعتبارسنجی، PDF، گواهی‌نامه، امضای دیجیتال validateSignature.tags=امضا، تأیید، اعتبارسنجی، PDF، گواهی‌نامه، امضای دیجیتال
#replace-invert-color
replace-color.title=جایگزینی/معکوس کردن رنگ replace-color.title=جایگزینی/معکوس کردن رنگ
replace-color.header=جایگزینی/معکوس کردن رنگ PDF replace-color.header=جایگزینی/معکوس کردن رنگ PDF
home.replaceColorPdf.title=جایگزینی و معکوس کردن رنگ home.replaceColorPdf.title=جایگزینی و معکوس کردن رنگ
@ -535,7 +537,6 @@ replace-color.submit=جایگزینی
########################### ###########################
# # # #
# WEB PAGES # # WEB PAGES #
@ -611,6 +612,7 @@ MarkdownToPDF.help=در حال پیشرفت
MarkdownToPDF.credit=از WeasyPrint استفاده می‌کند MarkdownToPDF.credit=از WeasyPrint استفاده می‌کند
#url-to-pdf #url-to-pdf
URLToPDF.title=URL به PDF URLToPDF.title=URL به PDF
URLToPDF.header=URL به PDF URLToPDF.header=URL به PDF
@ -903,7 +905,7 @@ compress.selectText.5=اندازه PDF مورد انتظار (مثلاً ۲۵MB
compress.submit=فشرده‌سازی compress.submit=فشرده‌سازی
# Add image #Add image
addImage.title=افزودن تصویر addImage.title=افزودن تصویر
addImage.header=افزودن تصویر به PDF addImage.header=افزودن تصویر به PDF
addImage.everyPage=هر صفحه؟ addImage.everyPage=هر صفحه؟
@ -911,7 +913,7 @@ addImage.upload=افزودن تصویر
addImage.submit=افزودن تصویر addImage.submit=افزودن تصویر
# Merge #merge
merge.title=ادغام merge.title=ادغام
merge.header=ادغام چندین PDF (۲+) merge.header=ادغام چندین PDF (۲+)
merge.sortByName=مرتب‌سازی بر اساس نام merge.sortByName=مرتب‌سازی بر اساس نام
@ -920,7 +922,7 @@ merge.removeCertSign=حذف امضای دیجیتال در فایل ادغام
merge.submit=ادغام merge.submit=ادغام
# PDF Organizer #pdfOrganiser
pdfOrganiser.title=سازماندهی صفحات pdfOrganiser.title=سازماندهی صفحات
pdfOrganiser.header=سازماندهی صفحات PDF pdfOrganiser.header=سازماندهی صفحات PDF
pdfOrganiser.submit=بازآرایی صفحات pdfOrganiser.submit=بازآرایی صفحات
@ -938,7 +940,7 @@ pdfOrganiser.mode.10=ادغام فرد-زوج
pdfOrganiser.placeholder=(مثال: ۱,۳,۲ یا ۴-۸,۲,۱۰-۱۲ یا 2n-1) pdfOrganiser.placeholder=(مثال: ۱,۳,۲ یا ۴-۸,۲,۱۰-۱۲ یا 2n-1)
# Multi Tool #multiTool
multiTool.title=ابزار چندگانه PDF multiTool.title=ابزار چندگانه PDF
multiTool.header=ابزار چندگانه PDF multiTool.header=ابزار چندگانه PDF
multiTool.uploadPrompts=نام فایل multiTool.uploadPrompts=نام فایل
@ -963,14 +965,24 @@ multiTool.dragDropMessage=صفحه(ها) انتخاب شده‌اند
multiTool.undo=واگرد multiTool.undo=واگرد
multiTool.redo=بازگرداندن multiTool.redo=بازگرداندن
# Multi Tool Advert #decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert
multiTool-advert.message=این ویژگی همچنین در <a href="{0}">صفحه ابزار چندگانه ما</a> موجود است. برای رابط کاربری صفحه به صفحه پیشرفته و ویژگی‌های اضافی بررسی کنید! multiTool-advert.message=این ویژگی همچنین در <a href="{0}">صفحه ابزار چندگانه ما</a> موجود است. برای رابط کاربری صفحه به صفحه پیشرفته و ویژگی‌های اضافی بررسی کنید!
# View PDF #view pdf
viewPdf.title=مشاهده PDF viewPdf.title=مشاهده PDF
viewPdf.header=مشاهده PDF viewPdf.header=مشاهده PDF
# Page Remover #pageRemover
pageRemover.title=حذف صفحات pageRemover.title=حذف صفحات
pageRemover.header=حذف صفحات PDF pageRemover.header=حذف صفحات PDF
pageRemover.pagesToDelete=صفحات برای حذف (یک لیست از اعداد صفحه جدا شده با کاما وارد کنید): pageRemover.pagesToDelete=صفحات برای حذف (یک لیست از اعداد صفحه جدا شده با کاما وارد کنید):
@ -978,14 +990,14 @@ pageRemover.submit=حذف صفحات
pageRemover.placeholder=(مثال: ۱,۲,۶ یا ۱-۱۰,۱۵-۳۰) pageRemover.placeholder=(مثال: ۱,۲,۶ یا ۱-۱۰,۱۵-۳۰)
# Rotate #rotate
rotate.title=چرخش PDF rotate.title=چرخش PDF
rotate.header=چرخش PDF rotate.header=چرخش PDF
rotate.selectAngle=زاویه چرخش را انتخاب کنید (به مضرب‌های ۹۰ درجه): rotate.selectAngle=زاویه چرخش را انتخاب کنید (به مضرب‌های ۹۰ درجه):
rotate.submit=چرخش rotate.submit=چرخش
# Split PDFs #split-pdfs
split.title=تقسیم PDF split.title=تقسیم PDF
split.header=تقسیم PDF split.header=تقسیم PDF
split.desc.1=اعدادی که انتخاب می‌کنید شماره صفحه‌هایی هستند که می‌خواهید بر روی آنها تقسیم انجام دهید split.desc.1=اعدادی که انتخاب می‌کنید شماره صفحه‌هایی هستند که می‌خواهید بر روی آنها تقسیم انجام دهید
@ -1014,7 +1026,7 @@ imageToPDF.selectText.4=ادغام در یک PDF واحد
imageToPDF.selectText.5=تبدیل به PDF های جداگانه imageToPDF.selectText.5=تبدیل به PDF های جداگانه
# PDF to Image #pdfToImage
pdfToImage.title=PDF به تصویر pdfToImage.title=PDF به تصویر
pdfToImage.header=PDF به تصویر pdfToImage.header=PDF به تصویر
pdfToImage.selectText=فرمت تصویر pdfToImage.selectText=فرمت تصویر
@ -1029,7 +1041,7 @@ pdfToImage.submit=تبدیل
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است. pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
# Add Password #addPassword
addPassword.title=افزودن گذرواژه addPassword.title=افزودن گذرواژه
addPassword.header=افزودن گذرواژه (رمزنگاری) addPassword.header=افزودن گذرواژه (رمزنگاری)
addPassword.selectText.1=انتخاب PDF برای رمزنگاری addPassword.selectText.1=انتخاب PDF برای رمزنگاری
@ -1051,7 +1063,7 @@ addPassword.selectText.16=محدودیت‌های باز شدن خود سند
addPassword.submit=رمزنگاری addPassword.submit=رمزنگاری
# Watermark #watermark
watermark.title=افزودن واترمارک watermark.title=افزودن واترمارک
watermark.header=افزودن واترمارک watermark.header=افزودن واترمارک
watermark.customColor=رنگ متن سفارشی watermark.customColor=رنگ متن سفارشی
@ -1070,7 +1082,7 @@ watermark.type.1=متن
watermark.type.2=تصویر watermark.type.2=تصویر
# Change Permissions #Change permissions
permissions.title=تغییر مجوزها permissions.title=تغییر مجوزها
permissions.header=تغییر مجوزها permissions.header=تغییر مجوزها
permissions.warning=برای اینکه این مجوزها غیرقابل تغییر باشند، توصیه می‌شود آنها را با گذرواژه از طریق صفحه افزودن گذرواژه تنظیم کنید permissions.warning=برای اینکه این مجوزها غیرقابل تغییر باشند، توصیه می‌شود آنها را با گذرواژه از طریق صفحه افزودن گذرواژه تنظیم کنید
@ -1087,7 +1099,7 @@ permissions.selectText.10=جلوگیری از چاپ فرمت‌های مختل
permissions.submit=تغییر permissions.submit=تغییر
# Remove Password #remove password
removePassword.title=حذف گذرواژه removePassword.title=حذف گذرواژه
removePassword.header=حذف گذرواژه (رمزگشایی) removePassword.header=حذف گذرواژه (رمزگشایی)
removePassword.selectText.1=PDFی را برای رمزگشایی انتخاب کنید removePassword.selectText.1=PDFی را برای رمزگشایی انتخاب کنید
@ -1095,7 +1107,7 @@ removePassword.selectText.2=گذرواژه
removePassword.submit=حذف removePassword.submit=حذف
# Change Metadata #changeMetadata
changeMetadata.title=عنوان: changeMetadata.title=عنوان:
changeMetadata.header=تغییر متاداده‌ها changeMetadata.header=تغییر متاداده‌ها
changeMetadata.selectText.1=لطفاً متغیرهایی که مایل به تغییر آنها هستید را ویرایش کنید changeMetadata.selectText.1=لطفاً متغیرهایی که مایل به تغییر آنها هستید را ویرایش کنید
@ -1114,7 +1126,7 @@ changeMetadata.selectText.5=افزودن ورودی متاداده سفارشی
changeMetadata.submit=تغییر changeMetadata.submit=تغییر
# PDF to PDF/A #pdfToPDFA
pdfToPDFA.title=PDF به PDF/A pdfToPDFA.title=PDF به PDF/A
pdfToPDFA.header=PDF به PDF/A pdfToPDFA.header=PDF به PDF/A
pdfToPDFA.credit=این سرویس از qpdf برای تبدیل PDF/A استفاده می‌کند pdfToPDFA.credit=این سرویس از qpdf برای تبدیل PDF/A استفاده می‌کند
@ -1124,7 +1136,7 @@ pdfToPDFA.outputFormat=فرمت خروجی
pdfToPDFA.pdfWithDigitalSignature=PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد. pdfToPDFA.pdfWithDigitalSignature=PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد.
# PDF to Word #PDFToWord
PDFToWord.title=PDF به ورد PDFToWord.title=PDF به ورد
PDFToWord.header=PDF به ورد PDFToWord.header=PDF به ورد
PDFToWord.selectText.1=فرمت فایل خروجی PDFToWord.selectText.1=فرمت فایل خروجی
@ -1132,7 +1144,7 @@ PDFToWord.credit=این سرویس از LibreOffice برای تبدیل فایل
PDFToWord.submit=تبدیل PDFToWord.submit=تبدیل
# PDF to Presentation #PDFToPresentation
PDFToPresentation.title=PDF به ارائه PDFToPresentation.title=PDF به ارائه
PDFToPresentation.header=PDF به ارائه PDFToPresentation.header=PDF به ارائه
PDFToPresentation.selectText.1=فرمت فایل خروجی PDFToPresentation.selectText.1=فرمت فایل خروجی
@ -1140,7 +1152,7 @@ PDFToPresentation.credit=این سرویس از LibreOffice برای تبدیل
PDFToPresentation.submit=تبدیل PDFToPresentation.submit=تبدیل
# PDF to Text #PDFToText
PDFToText.title=PDF به RTF (متن) PDFToText.title=PDF به RTF (متن)
PDFToText.header=PDF به RTF (متن) PDFToText.header=PDF به RTF (متن)
PDFToText.selectText.1=فرمت فایل خروجی PDFToText.selectText.1=فرمت فایل خروجی
@ -1148,27 +1160,26 @@ PDFToText.credit=این سرویس از LibreOffice برای تبدیل فایل
PDFToText.submit=تبدیل PDFToText.submit=تبدیل
# PDF to HTML #PDFToHTML
PDFToHTML.title=PDF به HTML PDFToHTML.title=PDF به HTML
PDFToHTML.header=PDF به HTML PDFToHTML.header=PDF به HTML
PDFToHTML.credit=این سرویس از pdftohtml برای تبدیل فایل استفاده می‌کند. PDFToHTML.credit=این سرویس از pdftohtml برای تبدیل فایل استفاده می‌کند.
PDFToHTML.submit=تبدیل PDFToHTML.submit=تبدیل
# PDF to XML #PDFToXML
PDFToXML.title=PDF به XML PDFToXML.title=PDF به XML
PDFToXML.header=PDF به XML PDFToXML.header=PDF به XML
PDFToXML.credit=این سرویس از LibreOffice برای تبدیل فایل استفاده می‌کند. PDFToXML.credit=این سرویس از LibreOffice برای تبدیل فایل استفاده می‌کند.
PDFToXML.submit=تبدیل PDFToXML.submit=تبدیل
# PDF to CSV #PDFToCSV
PDFToCSV.title=PDF به CSV PDFToCSV.title=PDF به CSV
PDFToCSV.header=PDF به CSV PDFToCSV.header=PDF به CSV
PDFToCSV.prompt=صفحه‌ای که می‌خواهید جدول استخراج شود را انتخاب کنید PDFToCSV.prompt=صفحه‌ای که می‌خواهید جدول استخراج شود را انتخاب کنید
PDFToCSV.submit=استخراج PDFToCSV.submit=استخراج
#split-by-size-or-count
# Split by Size or Count
split-by-size-or-count.title=تقسیم PDF بر اساس اندازه یا تعداد split-by-size-or-count.title=تقسیم PDF بر اساس اندازه یا تعداد
split-by-size-or-count.header=تقسیم PDF بر اساس اندازه یا تعداد split-by-size-or-count.header=تقسیم PDF بر اساس اندازه یا تعداد
split-by-size-or-count.type.label=انتخاب نوع تقسیم split-by-size-or-count.type.label=انتخاب نوع تقسیم
@ -1180,7 +1191,7 @@ split-by-size-or-count.value.placeholder=اندازه را وارد کنید (م
split-by-size-or-count.submit=ارسال split-by-size-or-count.submit=ارسال
# Overlay PDFs #overlay-pdfs
overlay-pdfs.header=ترکیب فایل‌های PDF overlay-pdfs.header=ترکیب فایل‌های PDF
overlay-pdfs.baseFile.label=انتخاب فایل پایه PDF overlay-pdfs.baseFile.label=انتخاب فایل پایه PDF
overlay-pdfs.overlayFiles.label=انتخاب فایل‌های ترکیبی PDF overlay-pdfs.overlayFiles.label=انتخاب فایل‌های ترکیبی PDF
@ -1196,7 +1207,7 @@ overlay-pdfs.position.background=پس‌زمینه
overlay-pdfs.submit=ارسال overlay-pdfs.submit=ارسال
# Split by Sections #split-by-sections
split-by-sections.title=تقسیم PDF به بخش‌ها split-by-sections.title=تقسیم PDF به بخش‌ها
split-by-sections.header=تقسیم PDF به بخش‌ها split-by-sections.header=تقسیم PDF به بخش‌ها
split-by-sections.horizontal.label=تقسیمات افقی split-by-sections.horizontal.label=تقسیمات افقی
@ -1207,7 +1218,7 @@ split-by-sections.submit=تقسیم PDF
split-by-sections.merge=ادغام به یک PDF split-by-sections.merge=ادغام به یک PDF
# Print File #printFile
printFile.title=چاپ فایل printFile.title=چاپ فایل
printFile.header=چاپ فایل به چاپگر printFile.header=چاپ فایل به چاپگر
printFile.selectText.1=انتخاب فایل برای چاپ printFile.selectText.1=انتخاب فایل برای چاپ
@ -1215,7 +1226,7 @@ printFile.selectText.2=نام چاپگر را وارد کنید
printFile.submit=چاپ printFile.submit=چاپ
# Licenses #licenses
licenses.nav=مجوزها licenses.nav=مجوزها
licenses.title=مجوزهای شخص ثالث licenses.title=مجوزهای شخص ثالث
licenses.header=مجوزهای شخص ثالث licenses.header=مجوزهای شخص ثالث
@ -1223,7 +1234,7 @@ licenses.module=ماژول
licenses.version=نسخه licenses.version=نسخه
licenses.license=مجوز licenses.license=مجوز
# Survey #survey
survey.nav=نظرسنجی survey.nav=نظرسنجی
survey.title=نظرسنجی Stirling-PDF survey.title=نظرسنجی Stirling-PDF
survey.description=Stirling-PDF هیچ ردیابی ندارد، بنابراین ما می‌خواهیم از کاربران خود بشنویم تا Stirling-PDF را بهبود دهیم! survey.description=Stirling-PDF هیچ ردیابی ندارد، بنابراین ما می‌خواهیم از کاربران خود بشنویم تا Stirling-PDF را بهبود دهیم!
@ -1235,7 +1246,7 @@ survey.button=شرکت در نظرسنجی
survey.dontShowAgain=دیگر نشان نده survey.dontShowAgain=دیگر نشان نده
# Error #error
error.sorry=متأسفیم برای مشکل موجود! error.sorry=متأسفیم برای مشکل موجود!
error.needHelp=نیاز به کمک / یافتن مشکلی؟ error.needHelp=نیاز به کمک / یافتن مشکلی؟
error.contactTip=اگر هنوز مشکلی دارید، دریغ نکنید که با ما تماس بگیرید. می‌توانید یک تیکت در صفحه GitHub ما ارسال کنید یا از طریق Discord با ما تماس بگیرید: error.contactTip=اگر هنوز مشکلی دارید، دریغ نکنید که با ما تماس بگیرید. می‌توانید یک تیکت در صفحه GitHub ما ارسال کنید یا از طریق Discord با ما تماس بگیرید:
@ -1249,14 +1260,13 @@ error.githubSubmit=GitHub - ارسال تیکت
error.discordSubmit=Discord - ارسال پست پشتیبانی error.discordSubmit=Discord - ارسال پست پشتیبانی
# Remove Image #remove-image
removeImage.title=حذف تصویر removeImage.title=حذف تصویر
removeImage.header=حذف تصویر removeImage.header=حذف تصویر
removeImage.removeImage=حذف تصویر removeImage.removeImage=حذف تصویر
removeImage.submit=حذف تصویر removeImage.submit=حذف تصویر
# Split by Chapters
splitByChapters.title=تقسیم PDF بر اساس فصل‌ها splitByChapters.title=تقسیم PDF بر اساس فصل‌ها
splitByChapters.header=تقسیم PDF بر اساس فصل‌ها splitByChapters.header=تقسیم PDF بر اساس فصل‌ها
splitByChapters.bookmarkLevel=سطح نشانک splitByChapters.bookmarkLevel=سطح نشانک
@ -1268,20 +1278,20 @@ splitByChapters.desc.3=شامل متادیتا: اگر انتخاب شده، م
splitByChapters.desc.4=اجازه‌ی تکرار: اگر انتخاب شده باشد، اجازه می‌دهد نشانک‌های متعدد در یک صفحه، فایل‌های PDF جداگانه ایجاد کنند. splitByChapters.desc.4=اجازه‌ی تکرار: اگر انتخاب شده باشد، اجازه می‌دهد نشانک‌های متعدد در یک صفحه، فایل‌های PDF جداگانه ایجاد کنند.
splitByChapters.submit=تقسیم PDF splitByChapters.submit=تقسیم PDF
# File Chooser #File Chooser
fileChooser.click=کلیک کنید fileChooser.click=کلیک کنید
fileChooser.or=یا fileChooser.or=یا
fileChooser.dragAndDrop=بکشید و رها کنید fileChooser.dragAndDrop=بکشید و رها کنید
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
# Release Notes #release notes
releases.footer=نسخه‌ها releases.footer=نسخه‌ها
releases.title=یادداشت‌های نسخه releases.title=یادداشت‌های نسخه
releases.header=یادداشت‌های نسخه releases.header=یادداشت‌های نسخه
releases.current.version=نسخه فعلی releases.current.version=نسخه فعلی
releases.note=یادداشت‌های نسخه فقط به زبان انگلیسی موجود است releases.note=یادداشت‌های نسخه فقط به زبان انگلیسی موجود است
# Validate Signature #Validate Signature
validateSignature.title=اعتبارسنجی امضاهای PDF validateSignature.title=اعتبارسنجی امضاهای PDF
validateSignature.header=اعتبارسنجی امضای دیجیتال validateSignature.header=اعتبارسنجی امضای دیجیتال
validateSignature.selectPDF=فایل PDF امضاشده را انتخاب کنید validateSignature.selectPDF=فایل PDF امضاشده را انتخاب کنید

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) sélectionnées
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles ! multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Pagina(e) selezionata(e)
multiTool.undo=Annulla multiTool.undo=Annulla
multiTool.redo=Rifai multiTool.redo=Rifai
#decrypt
decrypt.passwordPrompt=Questo file è protetto da password. Inserisci la password:
decrypt.cancelled=Operazione annullata per il PDF: {0}
decrypt.noPassword=Nessuna password fornita per il PDF crittografato: {0}
decrypt.invalidPassword=Riprova con la password corretta.
decrypt.invalidPasswordHeader=Password errata o crittografia non supportata per il PDF: {0}
decrypt.unexpectedError=Si è verificato un errore durante l'elaborazione del file. Riprova..
decrypt.serverError=Errore del server durante la decrittazione: {0}
decrypt.success=File decrittografato con successo.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive! multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!

View File

@ -56,12 +56,12 @@ userNotFoundMessage=ユーザーが見つかりません。
incorrectPasswordMessage=現在のパスワードが正しくありません。 incorrectPasswordMessage=現在のパスワードが正しくありません。
usernameExistsMessage=新しいユーザー名はすでに存在します。 usernameExistsMessage=新しいユーザー名はすでに存在します。
invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。 invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end. invalidPasswordMessage=パスワードは空にすることはできません。また、先頭・末尾にスペースを含めることもできません。
confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。 confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。 deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。 deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
disabledCurrentUserMessage=The current user cannot be disabled disabledCurrentUserMessage=現在のユーザーを無効にすることはできません
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。 downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。 userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。
userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。 userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。
@ -76,12 +76,12 @@ donate=寄付する
color= color=
sponsor=スポンサー sponsor=スポンサー
info=Info info=Info
pro=Pro pro=pro
page=Page page=ページ
pages=Pages pages=ページ
loading=Loading... loading=読込中...
addToDoc=Add to Document addToDoc=ドキュメントに追加
reset=Reset reset=リセット
legal.privacy=プライバシーポリシー legal.privacy=プライバシーポリシー
legal.terms=利用規約 legal.terms=利用規約
@ -92,7 +92,7 @@ legal.impressum=著作権利者情報
############### ###############
# Pipeline # # Pipeline #
############### ###############
pipeline.header=パイプラインメニュー (Alpha) pipeline.header=パイプラインメニュー (Beta)
pipeline.uploadButton=カスタムのアップロード pipeline.uploadButton=カスタムのアップロード
pipeline.configureButton=設定 pipeline.configureButton=設定
pipeline.defaultOption=カスタム pipeline.defaultOption=カスタム
@ -117,21 +117,21 @@ pipelineOptions.validateButton=検証
######################## ########################
# ENTERPRISE EDITION # # ENTERPRISE EDITION #
######################## ########################
enterpriseEdition.button=Upgrade to Pro enterpriseEdition.button=Proにアップグレード
enterpriseEdition.warning=This feature is only available to Pro users. enterpriseEdition.warning=この機能はProユーザーのみが利用できます。
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. enterpriseEdition.yamlAdvert=Stirling PDF Proは、YAML構成ファイルやその他のSSO機能をサポートしています。
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro enterpriseEdition.ssoAdvert=より多くのユーザー管理機能をお探しですか? Stirling PDF Proをご覧ください
################# #################
# Analytics # # Analytics #
################# #################
analytics.title=Do you want make Stirling PDF better? analytics.title=Stirling PDFをもっと良くしたいですか
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. analytics.paragraph1=Stirling PDFでは、製品の改善に役立つ分析機能をオプトインしています。個人情報やファイルの内容を追跡することはありません。
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. analytics.paragraph2=Stirling-PDFの成長を支援しユーザーをより深く理解できるように分析を有効にすることを検討してください。
analytics.enable=Enable analytics analytics.enable=分析を有効にする
analytics.disable=Disable analytics analytics.disable=分析を無効にする
analytics.settings=You can change the settings for analytics in the config/settings.yml file analytics.settings=config/settings.ymlファイルでアナリティクスの設定を変更できます。
############# #############
# NAVBAR # # NAVBAR #
@ -142,14 +142,14 @@ navbar.language=言語
navbar.settings=設定 navbar.settings=設定
navbar.allTools=ツール navbar.allTools=ツール
navbar.multiTool=マルチツール navbar.multiTool=マルチツール
navbar.search=Search navbar.search=検索
navbar.sections.organize=整理 navbar.sections.organize=整理
navbar.sections.convertTo=PDFへ変換 navbar.sections.convertTo=PDFへ変換
navbar.sections.convertFrom=PDFから変換 navbar.sections.convertFrom=PDFから変換
navbar.sections.security=署名とセキュリティ navbar.sections.security=署名とセキュリティ
navbar.sections.advance=アドバンスド navbar.sections.advance=アドバンスド
navbar.sections.edit=閲覧と編集 navbar.sections.edit=閲覧と編集
navbar.sections.popular=Popular navbar.sections.popular=人気
############# #############
# SETTINGS # # SETTINGS #
@ -208,7 +208,7 @@ adminUserSettings.user=ユーザー
adminUserSettings.addUser=新しいユーザを追加 adminUserSettings.addUser=新しいユーザを追加
adminUserSettings.deleteUser=ユーザの削除 adminUserSettings.deleteUser=ユーザの削除
adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか? adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled? adminUserSettings.confirmChangeUserStatus=ユーザーを無効/有効にする必要がありますか?
adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。 adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
adminUserSettings.roles=役割 adminUserSettings.roles=役割
adminUserSettings.role=役割 adminUserSettings.role=役割
@ -247,8 +247,8 @@ database.fileNotFound=ファイルが見つかりません
database.fileNullOrEmpty=ファイルは null または空であってはなりません database.fileNullOrEmpty=ファイルは null または空であってはなりません
database.failedImportFile=ファイルのインポートに失敗 database.failedImportFile=ファイルのインポートに失敗
session.expired=Your session has expired. Please refresh the page and try again. session.expired=セッションが期限切れです。ページを更新してもう一度お試しください。
session.refreshPage=Refresh Page session.refreshPage=ページを更新
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -488,52 +488,52 @@ overlay-pdfs.tags=Overlay
home.split-by-sections.title=PDFをセクションで分割 home.split-by-sections.title=PDFをセクションで分割
home.split-by-sections.desc=PDFの各ページを縦横に分割します。 home.split-by-sections.desc=PDFの各ページを縦横に分割します。
split-by-sections.tags=Section Split, Divide, Customize split-by-sections.tags=Section Split, Divide, Customize,Customise
home.AddStampRequest.title=PDFにスタンプを追加 home.AddStampRequest.title=PDFにスタンプを追加
home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize,Customise
home.PDFToBook.title=PDFを書籍に変換 home.PDFToBook.title=PDFを書籍に変換
home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
home.BookToPDF.title=PDFを書籍に変換 home.BookToPDF.title=PDFを書籍に変換
home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
home.removeImagePdf.title=画像の削除 home.removeImagePdf.title=画像の削除
home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします
removeImagePdf.tags=Remove Image,Page operations,Back end,server side removeImagePdf.tags=Remove Image,Page operations,Back end,server side
home.splitPdfByChapters.title=Split PDF by Chapters home.splitPdfByChapters.title=PDFをチャプターごとに分割
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. home.splitPdfByChapters.desc=チャプターの構造に基づいてPDFを複数のファイルに分割します
splitPdfByChapters.tags=split,chapters,bookmarks,organize splitPdfByChapters.tags=split,chapters,bookmarks,organize
home.validateSignature.title=Validate PDF Signature home.validateSignature.title=PDF署名の検証
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents home.validateSignature.desc=PDF文書のデジタル署名と証明書を検証します
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
#replace-invert-color #replace-invert-color
replace-color.title=Replace-Invert-Color replace-color.title=色の置換・反転
replace-color.header=Replace-Invert Color PDF replace-color.header=PDFの色の置換・反転
home.replaceColorPdf.title=Replace and Invert Color home.replaceColorPdf.title=色の置換と反転
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size home.replaceColorPdf.desc=PDF内のテキストと背景の色を置き換え、PDFのフルカラーを反転してファイルサイズを縮小します。
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
replace-color.selectText.1=Replace or Invert color Options replace-color.selectText.1=色の置換または反転オプション
replace-color.selectText.2=Default(Default high contrast colors) replace-color.selectText.2=デフォルト(デフォルトの高コントラスト色)
replace-color.selectText.3=Custom(Customized colors) replace-color.selectText.3=カスタム(カスタマイズされた色)
replace-color.selectText.4=Full-Invert(Invert all colors) replace-color.selectText.4=フル反転(すべての色を反転)
replace-color.selectText.5=High contrast color options replace-color.selectText.5=高コントラストカラーオプション
replace-color.selectText.6=white text on black background replace-color.selectText.6=黒背景に白文字
replace-color.selectText.7=Black text on white background replace-color.selectText.7=白背景に黒文字
replace-color.selectText.8=Yellow text on black background replace-color.selectText.8=黒背景に黄色文字
replace-color.selectText.9=Green text on black background replace-color.selectText.9=黒背景に緑文字
replace-color.selectText.10=Choose text Color replace-color.selectText.10=テキストの色を選択
replace-color.selectText.11=Choose background Color replace-color.selectText.11=背景色を選択
replace-color.submit=Replace replace-color.submit=置換
@ -560,9 +560,9 @@ login.oauth2AccessDenied=アクセス拒否
login.oauth2InvalidTokenResponse=無効なトークン応答 login.oauth2InvalidTokenResponse=無効なトークン応答
login.oauth2InvalidIdToken=無効なIDトークン login.oauth2InvalidIdToken=無効なIDトークン
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。 login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
login.alreadyLoggedIn=You are already logged in to login.alreadyLoggedIn=すでにログインしています
login.alreadyLoggedIn2=devices. Please log out of the devices and try again. login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。
login.toManySessions=You have too many active sessions login.toManySessions=アクティブなセッションが多すぎます
#auto-redact #auto-redact
autoRedact.title=自動塗りつぶし autoRedact.title=自動塗りつぶし
@ -578,8 +578,8 @@ autoRedact.submitButton=送信
#showJS #showJS
showJS.title=JavaScriptを表示 showJS.title=Javascriptを表示
showJS.header=JavaScriptを表示 showJS.header=Javascriptを表示
showJS.downloadJS=Javascriptをダウンロード showJS.downloadJS=Javascriptをダウンロード
showJS.submit=表示 showJS.submit=表示
@ -757,7 +757,7 @@ certSign.showSig=署名を表示
certSign.reason=理由 certSign.reason=理由
certSign.location=場所 certSign.location=場所
certSign.name=名前 certSign.name=名前
certSign.showLogo=Show Logo certSign.showLogo=ロゴを表示
certSign.submit=PDFに署名 certSign.submit=PDFに署名
@ -792,9 +792,9 @@ compare.highlightColor.2=ハイライトカラー 2:
compare.document.1=ドキュメント 1 compare.document.1=ドキュメント 1
compare.document.2=ドキュメント 2 compare.document.2=ドキュメント 2
compare.submit=比較 compare.submit=比較
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced compare.complex.message=提供された文書の一方または両方が大きなファイルであるため、比較の精度が低下する可能性があります。
compare.large.file.message=One or Both of the provided documents are too large to process compare.large.file.message=提供された文書の1つまたは両方が大きすぎて処理できません
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. compare.no.text.message=選択したPDFの1つまたは両方にテキストコンテンツがありません。比較するには、テキストを含むPDFを選択してください。
#BookToPDF #BookToPDF
BookToPDF.title=書籍やコミックをPDFに変換 BookToPDF.title=書籍やコミックをPDFに変換
@ -803,8 +803,8 @@ BookToPDF.credit=calibreを使用
BookToPDF.submit=変換 BookToPDF.submit=変換
#PDFToBook #PDFToBook
PDFToBook.title=書籍をPDFに変換 PDFToBook.title=PDFを書籍に変換
PDFToBook.header=書籍をPDFに変換 PDFToBook.header=PDFを書籍に変換
PDFToBook.selectText.1=フォーマット PDFToBook.selectText.1=フォーマット
PDFToBook.credit=calibreを使用 PDFToBook.credit=calibreを使用
PDFToBook.submit=変換 PDFToBook.submit=変換
@ -817,17 +817,17 @@ sign.draw=署名を書く
sign.text=テキスト入力 sign.text=テキスト入力
sign.clear=クリア sign.clear=クリア
sign.add=追加 sign.add=追加
sign.saved=Saved Signatures sign.saved=保存された署名
sign.save=Save Signature sign.save=署名を保存
sign.personalSigs=Personal Signatures sign.personalSigs=個人署名
sign.sharedSigs=Shared Signatures sign.sharedSigs=共有署名
sign.noSavedSigs=No saved signatures found sign.noSavedSigs=保存された署名が見つかりません
sign.addToAll=Add to all pages sign.addToAll=すべてのページに追加
sign.delete=Delete sign.delete=削除
sign.first=First page sign.first=最初のページ
sign.last=Last page sign.last=最後のページ
sign.next=Next page sign.next=次のページ
sign.previous=Previous page sign.previous=前のページ
#repair #repair
repair.title=修復 repair.title=修復
@ -944,29 +944,39 @@ pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
multiTool.title=PDFマルチツール multiTool.title=PDFマルチツール
multiTool.header=PDFマルチツール multiTool.header=PDFマルチツール
multiTool.uploadPrompts=ファイル名 multiTool.uploadPrompts=ファイル名
multiTool.selectAll=Select All multiTool.selectAll=すべて選択
multiTool.deselectAll=Deselect All multiTool.deselectAll=選択を解除
multiTool.selectPages=Page Select multiTool.selectPages=ページ選択
multiTool.selectedPages=Selected Pages multiTool.selectedPages=選択したページ
multiTool.page=Page multiTool.page=ページ
multiTool.deleteSelected=Delete Selected multiTool.deleteSelected=選択項目を削除
multiTool.downloadAll=Export multiTool.downloadAll=エクスポート
multiTool.downloadSelected=Export Selected multiTool.downloadSelected=選択項目をエクスポート
multiTool.insertPageBreak=Insert Page Break multiTool.insertPageBreak=改ページを挿入
multiTool.addFile=Add File multiTool.addFile=ファイルを追加
multiTool.rotateLeft=Rotate Left multiTool.rotateLeft=左回転
multiTool.rotateRight=Rotate Right multiTool.rotateRight=右回転
multiTool.split=Split multiTool.split=分割
multiTool.moveLeft=Move Left multiTool.moveLeft=左に移動
multiTool.moveRight=Move Right multiTool.moveRight=右に移動
multiTool.delete=Delete multiTool.delete=削除
multiTool.dragDropMessage=Page(s) Selected multiTool.dragDropMessage=選択されたページ
multiTool.undo=Undo multiTool.undo=元に戻す
multiTool.redo=Redo multiTool.redo=やり直す
#decrypt
decrypt.passwordPrompt=このファイルはパスワードで保護されています。パスワードを入力してください:
decrypt.cancelled=PDFの操作がキャンセルされました: {0}
decrypt.noPassword=暗号化されたPDFにパスワードが指定されていません: {0}
decrypt.invalidPassword=正しいパスワードでもう一度お試しください。
decrypt.invalidPasswordHeader=PDFのパスワードが正しくないか、暗号化がサポートされていません: {0}
decrypt.unexpectedError=ファイルの処理中にエラーが発生しました。もう一度お試しください。
decrypt.serverError=復号化中にサーバーエラーが発生しました: {0}
decrypt.success=ファイルの暗号化が正常に完了しました。
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=この機能は、<a href="{0}">マルチツール</a>でもご利用いただけます。強化されたページごとのUIと追加機能についてはこちらをご覧ください。
#view pdf #view pdf
viewPdf.title=PDFを表示 viewPdf.title=PDFを表示
@ -1122,8 +1132,8 @@ pdfToPDFA.header=PDFをPDF/Aに変換
pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。 pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。
pdfToPDFA.submit=変換 pdfToPDFA.submit=変換
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
pdfToPDFA.outputFormat=Output format pdfToPDFA.outputFormat=出力形式
pdfToPDFA.pdfWithDigitalSignature=PDF にはデジタル署名が含まれています。これは次の手順で削除されます。 pdfToPDFA.pdfWithDigitalSignature=PDFにはデジタル署名が含まれています。これは次の手順で削除されます。
#PDFToWord #PDFToWord
@ -1228,8 +1238,8 @@ licenses.license=ライセンス
survey.nav=アンケート survey.nav=アンケート
survey.title=Stirling-PDFのアンケート survey.title=Stirling-PDFのアンケート
survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: survey.changes=Stirling-PDFは前回の調査から変更されました。詳細についてはこちらのブログ投稿をご覧ください。
survey.changes2=With these changes we are getting paid business support and funding survey.changes2=これらの変更により私たちは有償のビジネスサポートと資金援助を受けています
survey.please=アンケートにご協力ください! survey.please=アンケートにご協力ください!
survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。) survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。)
survey.button=アンケートに答える survey.button=アンケートに答える
@ -1257,61 +1267,61 @@ removeImage.removeImage=画像の削除
removeImage.submit=画像を削除 removeImage.submit=画像を削除
splitByChapters.title=Split PDF by Chapters splitByChapters.title=PDFをチャプターごとに分割
splitByChapters.header=Split PDF by Chapters splitByChapters.header=PDFをチャプターごとに分割
splitByChapters.bookmarkLevel=Bookmark Level splitByChapters.bookmarkLevel=ブックマークレベル
splitByChapters.includeMetadata=Include Metadata splitByChapters.includeMetadata=メタデータを含める
splitByChapters.allowDuplicates=Allow Duplicates splitByChapters.allowDuplicates=重複を許可する
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. splitByChapters.desc.1=このツールは、チャプター構造に基づいてPDFファイルを複数のPDFに分割します。
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). splitByChapters.desc.2=ブックマークレベル:分割に使用するブックマークのレベルを選択します最上位レベルの場合は0、第2レベルの場合は1など
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.3=メタデータを含める:チェックすると、元のPDFのメタデータが各分割PDFに含まれます。
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.desc.4=重複を許可:チェックすると同じページ上の複数のブックマークから個別のPDFを作成できます。
splitByChapters.submit=Split PDF splitByChapters.submit=PDFを分割
#File Chooser #File Chooser
fileChooser.click=Click fileChooser.click=クリック
fileChooser.or=or fileChooser.or=または
fileChooser.dragAndDrop=Drag & Drop fileChooser.dragAndDrop=ドラッグ&ドロップ
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ
#release notes #release notes
releases.footer=Releases releases.footer=リリース
releases.title=Release Notes releases.title=リリースノート
releases.header=Release Notes releases.header=リリースノート
releases.current.version=Current Release releases.current.version=現在のリリース
releases.note=Release notes are only available in English releases.note=リリースノートは英語でのみで提供されています
#Validate Signature #Validate Signature
validateSignature.title=Validate PDF Signatures validateSignature.title=PDF署名の検証
validateSignature.header=Validate Digital Signatures validateSignature.header=デジタル署名の検証
validateSignature.selectPDF=Select signed PDF file validateSignature.selectPDF=署名済みPDFファイルを選択
validateSignature.submit=Validate Signatures validateSignature.submit=署名の検証
validateSignature.results=Validation Results validateSignature.results=検証結果
validateSignature.status=Status validateSignature.status=状態
validateSignature.signer=Signer validateSignature.signer=署名者
validateSignature.date=Date validateSignature.date=日付
validateSignature.reason=Reason validateSignature.reason=理由
validateSignature.location=Location validateSignature.location=場所
validateSignature.noSignatures=No digital signatures found in this document validateSignature.noSignatures=この文書にはデジタル署名が見つかりません
validateSignature.status.valid=Valid validateSignature.status.valid=有効
validateSignature.status.invalid=Invalid validateSignature.status.invalid=無効
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity validateSignature.chain.invalid=証明書チェーンの検証に失敗しました - 署名者の身元を確認できません
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified validateSignature.trust.invalid=証明書が信頼ストアにありません - ソースを検証できません
validateSignature.cert.expired=Certificate has expired validateSignature.cert.expired=証明書の有効期限が切れています
validateSignature.cert.revoked=Certificate has been revoked validateSignature.cert.revoked=証明書は取り消されました
validateSignature.signature.info=Signature Information validateSignature.signature.info=署名情報
validateSignature.signature=Signature validateSignature.signature=署名
validateSignature.signature.mathValid=Signature is mathematically valid BUT: validateSignature.signature.mathValid=署名は数学的には有効ですが:
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional) validateSignature.selectCustomCert=カスタム証明書ファイル X.509 (オプション)
validateSignature.cert.info=Certificate Details validateSignature.cert.info=証明書の詳細
validateSignature.cert.issuer=Issuer validateSignature.cert.issuer=発行者
validateSignature.cert.subject=Subject validateSignature.cert.subject=主題
validateSignature.cert.serialNumber=Serial Number validateSignature.cert.serialNumber=シリアルナンバー
validateSignature.cert.validFrom=Valid From validateSignature.cert.validFrom=有効開始日
validateSignature.cert.validUntil=Valid Until validateSignature.cert.validUntil=有効期限
validateSignature.cert.algorithm=Algorithm validateSignature.cert.algorithm=アルゴリズム
validateSignature.cert.keySize=Key Size validateSignature.cert.keySize=キーサイズ
validateSignature.cert.version=Version validateSignature.cert.version=バージョン
validateSignature.cert.keyUsage=Key Usage validateSignature.cert.keyUsage=キーの使用法
validateSignature.cert.selfSigned=Self-Signed validateSignature.cert.selfSigned=自己署名
validateSignature.cert.bits=bits validateSignature.cert.bits=ビット

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Página(s) Selecionadas
multiTool.undo=Desfazer multiTool.undo=Desfazer
multiTool.redo=Refazer multiTool.redo=Refazer
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Esta função também está disponível em <a href="{0}">Multiferramentas de PDF</a>. Com uma interface mais completa e funções adicionais. multiTool-advert.message=Esta função também está disponível em <a href="{0}">Multiferramentas de PDF</a>. Com uma interface mais completa e funções adicionais.

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

File diff suppressed because it is too large Load Diff

View File

@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
multiTool.undo=Undo multiTool.undo=Undo
multiTool.redo=Redo multiTool.redo=Redo
#decrypt
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
decrypt.cancelled=Operation cancelled for PDF: {0}
decrypt.noPassword=No password provided for encrypted PDF: {0}
decrypt.invalidPassword=Please try again with the correct password.
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
decrypt.unexpectedError=There was an error processing the file. Please try again.
decrypt.serverError=Server error while decrypting: {0}
decrypt.success=File decrypted successfully.
#multiTool-advert #multiTool-advert
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features! multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!

View File

@ -13,7 +13,7 @@
security: security:
enableLogin: false # set to 'true' to enable login enableLogin: false # set to 'true' to enable login
csrfDisabled: true # set to 'true' to disable CSRF protection (not recommended for production) csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1 loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2) loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
@ -102,7 +102,8 @@ metrics:
AutomaticallyGenerated: AutomaticallyGenerated:
key: example key: example
UUID: example UUID: example
appVersion: 0.35.0
processExecutor: processExecutor:
sessionLimit: # Process executor instances limits sessionLimit: # Process executor instances limits
libreOfficeSessionLimit: 1 libreOfficeSessionLimit: 1

View File

@ -65,6 +65,7 @@
overflow: hidden; overflow: hidden;
margin: -20px; margin: -20px;
padding: 20px; padding: 20px;
box-sizing:content-box;
} }
.feature-group-container.animated-group { .feature-group-container.animated-group {

Binary file not shown.

View File

@ -268,7 +268,7 @@ document.addEventListener("DOMContentLoaded", function () {
const parent = header.parentNode; const parent = header.parentNode;
const container = header.parentNode.querySelector(".feature-group-container"); const container = header.parentNode.querySelector(".feature-group-container");
if (parent.id !== "groupFavorites") { if (parent.id !== "groupFavorites") {
container.style.maxHeight = container.clientHeight + "px"; container.style.maxHeight = container.scrollHeight + "px";
} }
header.onclick = () => { header.onclick = () => {
expandCollapseToggle(parent); expandCollapseToggle(parent);

View File

@ -104,7 +104,7 @@ main() {
# run_tests "Stirling-PDF-Security" "./exampleYmlFiles/docker-compose-latest-security.yml" # run_tests "Stirling-PDF-Security" "./exampleYmlFiles/docker-compose-latest-security.yml"
# docker-compose -f "./exampleYmlFiles/docker-compose-latest-security.yml" down # docker-compose -f "./exampleYmlFiles/docker-compose-latest-security.yml" down
run_tests "Stirling-PDF-Security-Fat" "./exampleYmlFiles/docker-compose-latest-fat-security.yml" run_tests "Stirling-PDF-Security-Fat" "./exampleYmlFiles/test_cicd.yml"
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
cd cucumber cd cucumber
if python -m behave; then if python -m behave; then